нулевое или стандартное сравнение универсального аргумента в c #

<p>у меня есть общий метод, определенный следующим образом:</p><pre><code>public void mymethod<t>(t myargument) </code></pre><p>первое, что я хочу сделать, это проверить, является ли значение myargument значением по умолчанию для этого типа, примерно так:</p><pre><code>if (myargument == default(t)) </code></pre><p>но это не компилируется, потому что я не гарантировал, что t будет реализовывать оператор ==. поэтому я переключил код на это:</p><pre><code>if (myargument.equals(default(t))) </code></pre><p>теперь это скомпилируется, но потерпит неудачу, если myargument будет нулевым, что является частью того, что я тестирую я могу добавить явную проверку нуля следующим образом:</p><pre><code>if (myargument == null || myargument.equals(default(t))) </code></pre><p>теперь это кажется мне излишним. resharper даже предлагает мне заменить часть myargument == null на myargument == default (t), с которой я начал. есть ли лучший способ решить эту проблему?</p><p>мне нужно поддержать <b>и то и другое</b> ссылочные типы и типы значений.</p>

<p>чтобы избежать бокса, лучший способ сравнения обобщений на равенство - это <code>equalitycomparer<t>.default</code>. это учитывает <code>iequatable<t></code> (без упаковки), а также <code>object.equals</code> и обрабатывает все <code>nullable<t></code> «поднятые» нюансы. следовательно:</p><pre><code>if(equalitycomparer<t>.default.equals(obj, default(t))) { return obj; } </code></pre><p>это будет соответствовать:</p><ul> <li>нуль для классов</li> <li>ноль (пусто) для <code>nullable<t></code></li> <li>ноль/ложь/и т. д. для других структур</li> </ul>

<p>как насчет этого:</p><pre><code>if (object.equals(myargument, default(t))) { //... } </code></pre><p>использование метода <code>static object.equals()</code> исключает необходимость проверки <code>null</code> самостоятельно. явно квалифицировать вызов с помощью <code>object.</code>, вероятно, нет необходимости, в зависимости от вашего контекста, но я обычно префикс <code>static</code> вызовов с именем типа, просто чтобы сделать код более понятным.</p>

<p>мне удалось найти <a href="http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx">статью о microsoft connect</a> , в которой эта проблема обсуждается более подробно:</p><blockquote> <p>к сожалению, такое поведение является заданным, и не существует простого решения, позволяющего использовать параметры типа, которые могут содержать типы значений.</p> <p>если известно, что типы являются ссылочными типами, перегрузка по умолчанию определенных на объекте тестирует переменные на равенство ссылок, хотя тип может указывать свою собственную пользовательскую перегрузку. компилятор определяет, какую перегрузку использовать, основываясь на статическом типе переменной (определение не является полиморфным). поэтому, если вы измените свой пример, чтобы ограничить параметр универсального типа t незапечатанным ссылочным типом (таким как исключение), компилятор может определить конкретную перегрузку для использования, и следующий код скомпилирует:</p> </blockquote><pre><code>public class test<t> where t : exception </code></pre><blockquote> <p>если известно, что типы являются типами значений, выполняются специальные тесты на равенство значений на основе точных используемых типов. здесь нет хорошего сравнения «по умолчанию», поскольку ссылочные сравнения не имеют смысла для типов значений, и компилятор не может знать, какое конкретное сравнение значений вывести. компилятор может генерировать вызов valuetype.equals (object), но этот метод использует отражение и является довольно неэффективным по сравнению со сравнениями конкретных значений. следовательно, даже если бы вы указали ограничение типа значения для t, компилятору не следует создавать здесь ничего разумного:</p> </blockquote><pre><code>public class test<t> where t : struct </code></pre><blockquote> <p>в представленном вами случае, когда компилятор даже не знает, является ли t значением или ссылочным типом, аналогичным образом генерировать нечего, что было бы допустимо для всех возможных типов. сравнение ссылок не будет допустимым для типов значений, а какое-либо сравнение значений будет неожиданным для ссылочных типов, которые не перегружаются.</p> </blockquote><p>вот что вы можете сделать ...</p><p>я подтвердил, что оба эти метода работают для общего сравнения ссылочных типов и типов значений:</p><pre><code>object.equals(param, default(t)) </code></pre><p>или же</p><pre><code>equalitycomparer<t>.default.equals(param, default(t)) </code></pre><p>для сравнения с оператором "==" вам необходимо использовать один из следующих методов:</p><p>если все случаи t происходят из известного базового класса, вы можете сообщить об этом компилятору, используя ограничения универсального типа.</p><pre><code>public void mymethod<t>(t myargument) where t : mybase </code></pre><p>затем компилятор распознает, как выполнять операции с <code>mybase</code>, и не сгенерирует «оператор» == », который нельзя применить к операндам типа« t »и« t », которые вы видите сейчас.</p><p>другой вариант - ограничить t любым типом, который реализует <code>icomparable</code>.</p><pre><code>public void mymethod<t>(t myargument) where t : icomparable </code></pre><p>а затем используйте метод <code>compareto</code>, определенный в интерфейсе <a href="http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx">icomparable</a> .</p>

<p>попробуй это:</p><pre><code>if (equalitycomparer<t>.default.equals(myargument, default(t))) </code></pre><p>это должно скомпилировать, и делать то, что вы хотите.</p>

<p>(edited)</p><p>у марка гравелла лучший ответ, но я хотел опубликовать простой фрагмент кода, который я разработал, чтобы продемонстрировать его. просто запустите это в простом консольном приложении c #:</p><pre><code>public static class typehelper<t> { public static bool isdefault(t val) { return equalitycomparer<t>.default.equals(obj,default(t)); } } static void main(string[] args) { // value type console.writeline(typehelper<int>.isdefault(1)); //false console.writeline(typehelper<int>.isdefault(0)); // true // reference type console.writeline(typehelper<string>.isdefault("test")); //false console.writeline(typehelper<string>.isdefault(null)); //true //true console.readkey(); } </code></pre><p>еще одна вещь: может кто-то с vs2008 попробовать это в качестве метода расширения? я застрял здесь с 2005 годом, и мне любопытно посмотреть, будет ли это разрешено.</p><hr><p><strong> edit: </strong>вот как это работает, как метод расширения:</p><pre><code>using system; using system.collections.generic; class program { static void main() { // value type console.writeline(1.isdefault()); console.writeline(0.isdefault()); // reference type console.writeline("test".isdefault()); // null must be cast to a type console.writeline(((string)null).isdefault()); } } // the type cannot be generic public static class typehelper { // i made the method generic instead public static bool isdefault<t>(this t val) { return equalitycomparer<t>.default.equals(val, default(t)); } } </code></pre>

<p>для обработки всех типов t, включая те, где t является примитивным типом, вам нужно скомпилировать оба метода сравнения:</p><pre><code> t get<t>(func<t> createobject) { t obj = createobject(); if (obj == null || obj.equals(default(t))) return obj; // .. do a bunch of stuff return obj; } </code></pre>

<p>здесь будет проблема -</p><p>если вы хотите, чтобы это работало для любого типа, default (t) всегда будет нулевым для ссылочных типов и 0 (или структура, полная 0) для типов значений.</p><p>это, вероятно, не поведение, которое вы после, хотя. если вы хотите, чтобы это работало универсальным образом, вам, вероятно, нужно использовать отражение, чтобы проверить тип t, и обрабатывать типы значений, отличные от ссылочных типов.</p><p>в качестве альтернативы вы можете наложить на это ограничение интерфейса, и этот интерфейс может обеспечить способ проверки по умолчанию класса/структуры.</p>

<p>я думаю, что вам, вероятно, нужно разделить эту логику на две части и сначала проверить на ноль.</p><pre><code>public static bool isnullorempty<t>(t value) { if (isnull(value)) { return true; } if (value is string) { return string.isnullorempty(value as string); } return value.equals(default(t)); } public static bool isnull<t>(t value) { if (value is valuetype) { return false; } return null == (object)value; } </code></pre><p>в методе isnull мы полагаемся на тот факт, что объекты valuetype не могут быть нулевыми по определению, поэтому, если значение оказывается классом, производным от valuetype, мы уже знаем, что он не равен нулю. с другой стороны, если это не тип значения, тогда мы можем просто сравнить приведенное значение для объекта с нулевым значением. мы могли бы избежать проверки valuetype, перейдя прямо к приведению к объекту, но это означало бы, что тип значения будет упакован, что мы, вероятно, хотим избежать, поскольку это подразумевает, что новый объект создается в куче.</p><p>в методе isnullorempty мы проверяем особый случай строки. для всех других типов мы сравниваем значение (которое уже известно как<em> not </em>null) с его значением по умолчанию, которое для всех ссылочных типов равно нулю, а для типов значений обычно является некоторой формой нуля (если они являются целыми ).</p><p>используя эти методы, следующий код ведет себя так, как вы могли ожидать:</p><pre><code>class program { public class myclass { public string mystring { get; set; } } static void main() { int i1 = 1; test("i1", i1); // false int i2 = 0; test("i2", i2); // true int? i3 = 2; test("i3", i3); // false int? i4 = null; test("i4", i4); // true console.writeline(); string s1 = "hello"; test("s1", s1); // false string s2 = null; test("s2", s2); // true string s3 = string.empty; test("s3", s3); // true string s4 = ""; test("s4", s4); // true console.writeline(); myclass mc1 = new myclass(); test("mc1", mc1); // false myclass mc2 = null; test("mc2", mc2); // true } public static void test<t>(string fieldname, t field) { console.writeline(fieldname + ": " + isnullorempty(field)); } // public static bool isnullorempty<t>(t value) ... // public static bool isnull<t>(t value) ... } </code></pre>

<p>я использую:</p><pre><code>public class myclass<t> { private bool isnull() { var nullable = nullable.getunderlyingtype(typeof(t)) != null; return nullable ? equalitycomparer<t>.default.equals(value, default(t)) : false; } } </code></pre>