我有一个像这样定义的泛型方法:
public void MyMethod<T>(T myArgument)
我要做的第一件事是检查myArgument的值是否是该类型的默认值,如下所示:
if (myArgument == default(T))
但是这不能编译,因为我没有保证T会实现==运算符。所以我把代码改为:
if (myArgument.Equals(default(T)))
现在这个编译,但如果myArgument为null,则会失败,这是我正在测试的一部分。我可以像这样添加一个显式的空检查:
if (myArgument == null || myArgument.Equals(default(T)))
现在这让我感到多余。 ReSharper甚至建议我将myArgument == null部分更改为myArgument == default(T),这是我开始的地方。有没有更好的方法来解决这个问题?
我需要支持两种引用类型和值类型。
答案 0 :(得分:502)
为了避免装箱,比较泛型的最佳方法是使用EqualityComparer<T>.Default
。这尊重IEquatable<T>
(没有装箱)以及object.Equals
,并处理所有Nullable<T>
“解除”的细微差别。因此:
if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
return obj;
}
这将匹配:
Nullable<T>
答案 1 :(得分:113)
这个怎么样:
if (object.Equals(myArgument, default(T)))
{
//...
}
使用static object.Equals()
方法可以避免您自己进行null
检查。根据您的上下文,可能没有必要使用object.
明确限定调用,但我通常使用类型名称对static
调用进行前缀,以使代码更易于使用。
答案 2 :(得分:24)
我能够找到一个Microsoft Connect article来详细讨论这个问题:
不幸的是,这种行为是设计上的,并且没有一个简单的解决方案可以使用包含值类型的类型参数。
如果已知类型是引用类型,则对象上定义的默认重载测试变量以引用相等,尽管类型可以指定自己的自定义重载。编译器根据变量的静态类型确定要使用的重载(确定不是多态的)。因此,如果您更改示例以将泛型类型参数T约束为非密封引用类型(例如Exception),则编译器可以确定要使用的特定重载,并且以下代码将编译:
public class Test<T> where T : Exception
如果已知类型是值类型,则根据使用的确切类型执行特定值相等性测试。这里没有好的“默认”比较,因为参考比较对值类型没有意义,并且编译器无法知道要发出哪个特定值比较。编译器可以发出对ValueType.Equals(Object)的调用,但是此方法使用反射,并且与特定值比较相比效率很低。因此,即使您要在T上指定值类型约束,编译器也无法在此处生成:
public class Test<T> where T : struct
在您提出的情况下,编译器甚至不知道T是值还是引用类型,同样没有什么可生成的,它对所有可能的类型都有效。参考比较对于值类型无效,对于不重载的引用类型,某种值比较会出乎意料。
这是你可以做的......
我已经验证这两种方法都适用于参考和值类型的通用比较:
object.Equals(param, default(T))
或
EqualityComparer<T>.Default.Equals(param, default(T))
要与“==”运算符进行比较,您需要使用以下方法之一:
如果T的所有情况都来自已知的基类,您可以让编译器知道使用泛型类型限制。
public void MyMethod<T>(T myArgument) where T : MyBase
编译器然后识别如何在MyBase
上执行操作,并且不会抛出“运算符'=='不能应用于现在看到的'T'和'T'类型的操作数错误。
另一种选择是将T限制为任何实现IComparable
的类型。
public void MyMethod<T>(T myArgument) where T : IComparable
然后使用IComparable interface定义的CompareTo
方法。
答案 3 :(得分:18)
试试这个:
if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))
应编译,并做你想做的事。
答案 4 :(得分:7)
(编辑)的
Marc Gravell有最好的答案,但我想发布一个简单的代码片段,我正在努力演示它。只需在一个简单的C#控制台应用程序中运行它:
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();
}
还有一件事:VS2008的用户可以尝试将其作为扩展方法吗?我在这里坚持2005年,我很想知道是否允许这样做。
修改:以下是如何将其作为扩展方法使用:
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));
}
}
答案 5 :(得分:6)
要处理所有类型的T,包括T是基本类型,你需要在两种比较方法中进行编译:
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;
}
答案 6 :(得分:3)
基于公认答案的扩展方法。
public static bool IsDefault<T>(this T inObj)
{
return EqualityComparer<T>.Default.Equals(inObj, default);
}
用法:
private bool SomeMethod(){
var tValue = GetMyObject<MyObjectType>();
if (tValue == null || tValue.IsDefault()) return false;
}
使用null替代以简化操作:
public static bool IsNullOrDefault<T>(this T inObj)
{
if (inObj == null) return true;
return EqualityComparer<T>.Default.Equals(inObj, default);
}
用法:
private bool SomeMethod(){
var tValue = GetMyObject<MyObjectType>();
if (tValue.IsNullOrDefault()) return false;
}
答案 7 :(得分:2)
这里会出现问题 -
如果你要允许它适用于任何类型,默认(T)对于引用类型将始终为null,对于值类型则为0(或struct full of 0)。
但这可能不是你追求的行为。如果您希望以通用方式工作,则可能需要使用反射来检查T的类型,并处理与引用类型不同的值类型。
或者,您可以在此处设置接口约束,并且接口可以提供一种方法来检查类/结构的默认值。
答案 8 :(得分:1)
我认为您可能需要将此逻辑拆分为两部分并首先检查null。
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;
}
在IsNull方法中,我们依赖于ValueType对象的定义不能为null的事实,因此如果value恰好是从ValueType派生的类,我们已经知道它不是null。另一方面,如果它不是值类型,那么我们可以将对象的值转换为null。我们可以通过直接转换为对象来避免对ValueType的检查,但这意味着值类型将被装箱,这是我们可能想要避免的,因为它意味着在堆上创建了一个新对象。
在IsNullOrEmpty方法中,我们正在检查字符串的特殊情况。对于所有其他类型,我们将值(已知为 not null)与其默认值进行比较,对于所有引用类型,该值为null,对于值类型,通常为某种形式为零(如果它们'积分)。
使用这些方法,以下代码的行为与您预期的一样:
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) ...
}
答案 9 :(得分:0)
我使用:
public class MyClass<T>
{
private bool IsNull()
{
var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
}
}
答案 10 :(得分:0)
只是一个简单的回答,也是对我自己的提醒。 但我发现这对我的项目很有帮助。 我这样写的原因是因为如果值为 0,我不希望默认整数 0 被标记为 null
private static int o;
public static void Main()
{
//output: IsNull = False -> IsDefault = True
Console.WriteLine( "IsNull = " + IsNull( o ) + " -> IsDefault = " + IsDefault(o));
}
public static bool IsNull<T>(T paramValue)
{
if( string.IsNullOrEmpty(paramValue + "" ))
return true;
return false;
}
public static bool IsDefault<T>(T val)
{
return EqualityComparer<T>.Default.Equals(val, default(T));
}
答案 11 :(得分:-1)
不知道这是否符合您的要求,但您可以将T约束为实现IComparable等接口的Type,然后使用该接口的ComparesTo()方法(IIRC支持/处理空值) )像这样:
public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))
您可以使用其他界面,IEquitable等等。
答案 12 :(得分:-2)
@ilitirit:
public class Class<T> where T : IComparable
{
public T Value { get; set; }
public void MyMethod(T val)
{
if (Value == val)
return;
}
}
运算符'=='不能应用于'T'和'T'类型的操作数
如果没有显式的null测试,然后按照上面的建议调用Equals方法或object.Equals,我无法想到这样做的方法。
您可以使用System.Comparison设计一个解决方案,但实际上最终将采用更多代码行并大幅提高复杂性。
答案 13 :(得分:-3)
我认为你很接近。
if (myArgument.Equals(default(T)))
现在编译,但如果myArgument
为null,则会失败,这是我正在测试的一部分。我可以像这样添加一个显式的空检查:
您只需要反转调用equals的对象,以获得优雅的零安全方法。
default(T).Equals(myArgument);