Ответ 1
Если бокс не является проблемой, вы можете просто использовать:
object.Equals(value, default(T))
Мне нужно проверить общий объект на null или по умолчанию (T). Но у меня есть проблема... В настоящее время я сделал это вот так:
if (typeof(T).IsValueType)
{
if(default(T).Equals(thing))
// Do something
else
// Do something else
}
else
{
if(thing == null)
// Do something
else
// Do something else
}
Но потом я в конечном итоге повторяю себя... что мне не нравится. Проблема заключается в следующем:
thing == null;
Здесь ReSharper предупреждает о возможном сравнении типа значения с 'null'.
thing == default(T);
Здесь я получаю ошибку компилятора: не могу применить оператор '==' к операндам типа 'T' и 'T'.
thing.Equals(null|default(T));
thing
может, очевидно, быть нулевым (вот почему я должен проверить!), поэтому вызовет исключение NullReferenceException.
null|default(T).Equals(thing);
null и значение по умолчанию (T) очень часто равно null...
Есть ли чистый способ сделать это?
Если бокс не является проблемой, вы можете просто использовать:
object.Equals(value, default(T))
Правильный способ сделать это:
return EqualityComparer<T>.Default.Equals(value, default(T))
Нет бокса. Вы даже можете определить способ расширения следующим образом:
public static void bool IsDefault<T>(this T value)
{
return EqualityComparer<T>.Default.Equals(value, default(T));
}
.. и вызовите его следующим образом:
return entry.IsDefault();
Хотя мне лично не нужны методы расширения на T (например, этот объект IsNull()), поскольку он иногда затрудняет чтение.
Когда мне нужно проверить, является ли значение NULL, я использую метод ниже. Обычно я использую это при вызове методов, которые принимают любой тип, но не нулевые, такие как кеш.
public static bool IsNull<T>(this T value)
{
var type = typeof(T);
return (type.IsClass || Nullable.GetUnderlyingType(type) != null)
&& EqualityComparer<T>.Default.Equals(value, default(T));
}
Лучшая вещь, о которой я могу думать в данный момент:
return value == null || value.Equals(default(T));
Edit:
По-видимому, существует статический метод object.Equals
, о котором я не знал:
return object.Equals(value, default(T));
Это лучше.
Немного бокса сделает работу просто отлично.
static bool IsNullOrDefault<T>(T value)
{
return ((object)default(T)) == null ?
((object)value) == null :
default(T).Equals(value);
}
Вы можете вообще избегать бокса, заметив, что неопределенность типа может быть определена статически.
Вот что вы можете сделать:
isDefault
типа Predicate<T>
в вашем общем классеv==null
или default(T).Equals(v)
в зависимости от результатаisDefault(x)
вместо x==null
в остальной части вашего кодаВот пример:
public class Example<T> {
private static readonly Predicate<T> isDefault;
static Example() {
// Nullability check is a bit ugly, but we do it once per T,
// so what the heck...
if (typeof(T).IsValueType &&
(!typeof(T).IsGenericType
|| typeof(T).GetGenericTypeDefinition() != typeof(Nullable<>)
)) {
// This is safe because T is not null
isDefault = val => default(T).Equals(val);
} else {
// T is not a value type, so its default is null
isDefault = val => val == null;
}
}
public bool Check(T value) {
// Now our null-checking is both good-looking and efficient
return isDefault(value);
}
}
С тестами:
public class DefaultOrNullChecker<T> {
public bool Check(object x) {
return object.ReferenceEquals(x, null) || x.Equals(default(T));
}
}
[TestFixture]
public class Tests {
[Test] public void when_T_is_reference_type() {
Assert.IsFalse(new DefaultOrNullChecker<Exception>().Check(new Exception()));}
[Test] public void when_T_is_value_type() {
Assert.IsFalse(new DefaultOrNullChecker<int>().Check(123)); }
[Test] public void when_T_is_null() {
Assert.IsTrue(new DefaultOrNullChecker<Exception>().Check(null));}
[Test] public void when_T_is_default_value() {
Assert.IsTrue(new DefaultOrNullChecker<int>().Check(0)); }
}
Что не так с этим?
if (thing == default(T)) { }
Если это тип значения, JIT просто удалит оператор вообще.