我有一个像这样定义的泛型方法:
public void MyMethod<T>(T myArgument)
Run Code Online (Sandbox Code Playgroud)
我想要做的第一件事是检查myArgument的值是否是该类型的默认值,如下所示:
if (myArgument == default(T))
Run Code Online (Sandbox Code Playgroud)
但是这不能编译,因为我没有保证T将实现==运算符.所以我把代码改为:
if (myArgument.Equals(default(T)))
Run Code Online (Sandbox Code Playgroud)
现在这个编译,但是如果myArgument为null则会失败,这是我正在测试的一部分.我可以像这样添加一个显式的空检查:
if (myArgument == null || myArgument.Equals(default(T)))
Run Code Online (Sandbox Code Playgroud)
现在这让我感到多余.ReSharper甚至建议我将myArgument == null部分更改为myArgument == default(T),这是我开始的地方.有没有更好的方法来解决这个问题?
我需要支持两种引用类型和值类型.
Mar*_*ell 539
为了避免装箱,比较泛型的最佳方法是使用EqualityComparer<T>.Default.这尊重IEquatable<T>(没有拳击)以及object.Equals处理所有Nullable<T>"提升"的细微差别.因此:
if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
return obj;
}
Run Code Online (Sandbox Code Playgroud)
这将匹配:
Nullable<T>Ken*_*art 117
这个怎么样:
if (object.Equals(myArgument, default(T)))
{
//...
}
Run Code Online (Sandbox Code Playgroud)
使用该static object.Equals()方法可以避免您自己进行null检查.object.根据您的上下文,可能没有必要显式地限定调用,但我通常static使用类型名称为调用添加前缀,以使代码更易于解析.
Eri*_*ver 27
我能够找到一篇详细讨论此问题的Microsoft Connect文章:
不幸的是,这种行为是设计上的,并没有一个简单的解决方案来启用可能包含值类型的类型参数.
如果已知类型是引用类型,则对象上定义的默认重载测试变量以引用相等,尽管类型可以指定自己的自定义重载.编译器根据变量的静态类型确定要使用的重载(确定不是多态的).因此,如果您更改示例以将泛型类型参数T约束为非密封引用类型(例如Exception),则编译器可以确定要使用的特定重载,以下代码将编译:
public class Test<T> where T : Exception
Run Code Online (Sandbox Code Playgroud)
如果已知类型是值类型,则根据使用的确切类型执行特定值相等性测试.这里没有好的"默认"比较,因为参考比较对值类型没有意义,并且编译器无法知道要发出哪个特定值比较.编译器可以发出对ValueType.Equals(Object)的调用,但是此方法使用反射,并且与特定值比较相比效率很低.因此,即使您要在T上指定值类型约束,编译器也无法在此处生成:
public class Test<T> where T : struct
Run Code Online (Sandbox Code Playgroud)
在您提供的情况下,编译器甚至不知道T是值还是引用类型,同样没有任何生成可以对所有可能类型有效的内容.参考比较对于值类型无效,对于不重载的引用类型,某种值比较会出乎意料.
这是你可以做的......
我已经验证了这两种方法都可以用于参考和值类型的通用比较:
object.Equals(param, default(T))
Run Code Online (Sandbox Code Playgroud)
要么
EqualityComparer<T>.Default.Equals(param, default(T))
Run Code Online (Sandbox Code Playgroud)
要与"=="运算符进行比较,您需要使用以下方法之一:
如果T的所有情况都来自已知的基类,您可以让编译器知道使用泛型类型限制.
public void MyMethod<T>(T myArgument) where T : MyBase
Run Code Online (Sandbox Code Playgroud)
编译器然后识别如何执行操作,MyBase并且不会抛出"运算符'=='不能应用于现在看到的'T'和'T'类型的操作数"错误.
另一种选择是将T限制为任何实现的类型IComparable.
public void MyMethod<T>(T myArgument) where T : IComparable
Run Code Online (Sandbox Code Playgroud)
然后使用IComparable接口CompareTo定义的方法.
ang*_*son 18
试试这个:
if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))
Run Code Online (Sandbox Code Playgroud)
应该编译,并做你想要的.
(编辑)的
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();
}
Run Code Online (Sandbox Code Playgroud)
还有一件事:有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));
}
}
Run Code Online (Sandbox Code Playgroud)
要处理所有类型的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;
}
Run Code Online (Sandbox Code Playgroud)
基于已接受答案的扩展方法。
public static bool IsDefault<T>(this T inObj)
{
return EqualityComparer<T>.Default.Equals(inObj, default);
}
Run Code Online (Sandbox Code Playgroud)
用法:
private bool SomeMethod(){
var tValue = GetMyObject<MyObjectType>();
if (tValue == null || tValue.IsDefault()) return false;
}
Run Code Online (Sandbox Code Playgroud)
与 null 交替以简化:
public static bool IsNullOrDefault<T>(this T inObj)
{
if (inObj == null) return true;
return EqualityComparer<T>.Default.Equals(inObj, default);
}
Run Code Online (Sandbox Code Playgroud)
用法:
private bool SomeMethod(){
var tValue = GetMyObject<MyObjectType>();
if (tValue.IsNullOrDefault()) return false;
}
Run Code Online (Sandbox Code Playgroud)