C# 中泛型参数的空值或默认比较
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/65351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Null or default comparison of generic argument in C#
提问by Stefan Moser
I have a generic method defined like this:
我有一个这样定义的通用方法:
public void MyMethod<T>(T myArgument)
The first thing I want to do is check if the value of myArgument is the default value for that type, something like this:
我想做的第一件事是检查 myArgument 的值是否是该类型的默认值,如下所示:
if (myArgument == default(T))
But this doesn't compile because I haven't guaranteed that T will implement the == operator. So I switched the code to this:
但这不会编译,因为我没有保证 T 会实现 == 运算符。所以我把代码改成这样:
if (myArgument.Equals(default(T)))
Now this compiles, but will fail if myArgument is null, which is part of what I'm testing for. I can add an explicit null check like this:
现在编译,但如果 myArgument 为 null 会失败,这是我测试的一部分。我可以像这样添加一个显式的空检查:
if (myArgument == null || myArgument.Equals(default(T)))
Now this feels redundant to me. ReSharper is even suggesting that I change the myArgument == null part into myArgument == default(T) which is where I started. Is there a better way to solve this problem?
现在这对我来说是多余的。ReSharper 甚至建议我将 myArgument == null 部分更改为 myArgument == default(T) ,这是我开始的地方。有没有更好的方法来解决这个问题?
I need to support bothreferences types and value types.
我需要支持两种引用类型和值类型。
采纳答案by Marc Gravell
To avoid boxing, the best way to compare generics for equality is with EqualityComparer<T>.Default
. This respects IEquatable<T>
(without boxing) as well as object.Equals
, and handles all the Nullable<T>
"lifted" nuances. Hence:
为了避免装箱,比较泛型是否相等的最好方法是使用EqualityComparer<T>.Default
. 这尊重IEquatable<T>
(没有拳击)以及object.Equals
,并处理所有Nullable<T>
“提升”的细微差别。因此:
if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
return obj;
}
This will match:
这将匹配:
- null for classes
- null (empty) for
Nullable<T>
- zero/false/etc for other structs
- 类为空
- 空(空)为
Nullable<T>
- 其他结构的零/假/等
回答by Eric Schoonover
I was able to locate a Microsoft Connect articlethat discusses this issue in some detail:
我找到了一篇详细讨论此问题的Microsoft Connect 文章:
Unfortunately, this behavior is by design and there is not an easy solution to enable use of with type parameters that may contain value types.
If the types are known to be reference types, the default overload of defined on object tests variables for reference equality, although a type may specify its own custom overload. The compiler determines which overload to use based on the static type of the variable (the determination is not polymorphic). Therefore, if you change your example to constrain the generic type parameter T to a non-sealed reference type (such as Exception), the compiler can determine the specific overload to use and the following code would compile:
不幸的是,这种行为是设计使然,并没有一个简单的解决方案来启用可能包含值类型的类型参数。
如果已知类型是引用类型,则定义在对象上的默认重载会测试变量的引用相等性,尽管类型可以指定其自己的自定义重载。编译器根据变量的静态类型确定要使用的重载(该确定不是多态的)。因此,如果您更改示例以将泛型类型参数 T 限制为非密封引用类型(例如 Exception),则编译器可以确定要使用的特定重载,并且将编译以下代码:
public class Test<T> where T : Exception
If the types are known to be value types, performs specific value equality tests based on the exact types used. There is no good "default" comparison here since reference comparisons are not meaningful on value types and the compiler cannot know which specific value comparison to emit. The compiler could emit a call to ValueType.Equals(Object) but this method uses reflection and is quite inefficient compared to the specific value comparisons. Therefore, even if you were to specify a value-type constraint on T, there is nothing reasonable for the compiler to generate here:
如果已知类型是值类型,则根据所使用的确切类型执行特定的值相等性测试。这里没有好的“默认”比较,因为引用比较对值类型没有意义,编译器不知道要发出哪个特定的值比较。编译器可以发出对 ValueType.Equals(Object) 的调用,但此方法使用反射,并且与特定值比较相比效率很低。因此,即使您要在 T 上指定值类型约束,编译器在此处生成也不合理:
public class Test<T> where T : struct
In the case you presented, where the compiler does not even know whether T is a value or reference type, there is similarly nothing to generate that would be valid for all possible types. A reference comparison would not be valid for value types and some sort of value comparison would be unexpected for reference types that do not overload.
在您提供的情况下,编译器甚至不知道 T 是值类型还是引用类型,因此类似地没有生成对所有可能类型都有效的内容。引用比较对于值类型是无效的,并且对于不重载的引用类型来说,某种值比较是意外的。
Here is what you can do...
这是你可以做的......
I have validated that both of these methods work for a generic comparison of reference and value types:
我已经验证这两种方法都适用于引用和值类型的通用比较:
object.Equals(param, default(T))
or
或者
EqualityComparer<T>.Default.Equals(param, default(T))
To do comparisons with the "==" operator you will need to use one of these methods:
要与“==”运算符进行比较,您需要使用以下方法之一:
If all cases of T derive from a known base class you can let the compiler know using generic type restrictions.
如果 T 的所有情况都源自已知的基类,您可以使用泛型类型限制让编译器知道。
public void MyMethod<T>(T myArgument) where T : MyBase
The compiler then recognizes how to perform operations on MyBase
and will not throw the "Operator '==' cannot be applied to operands of type 'T' and 'T'" error that you are seeing now.
然后编译器会识别如何对其执行操作,MyBase
并且不会抛出您现在看到的“运算符 '==' 无法应用于类型为 'T' 和 'T' 的操作数”错误。
Another option would be to restrict T to any type that implements IComparable
.
另一种选择是将 T 限制为任何实现IComparable
.
public void MyMethod<T>(T myArgument) where T : IComparable
And then use the CompareTo
method defined by the IComparable interface.
然后使用IComparable 接口CompareTo
定义的方法。
回答by Kent Boogaart
How about this:
这个怎么样:
if (object.Equals(myArgument, default(T)))
{
//...
}
Using the static object.Equals()
method avoids the need for you to do the null
check yourself. Explicitly qualifying the call with object.
probably isn't necessary depending on your context, but I normally prefix static
calls with the type name just to make the code more soluble.
使用该static object.Equals()
方法可避免您自己进行null
检查。object.
根据您的上下文,可能不需要显式限定调用,但我通常在static
调用前加上类型名称,只是为了使代码更易于理解。
回答by caryden
Don't know if this works with your requirements or not, but you could constrain T to be a Type that implements an interface such as IComparable and then use the ComparesTo() method from that interface (which IIRC supports/handles nulls) like this:
不知道这是否符合您的要求,但您可以将 T 约束为实现 IComparable 等接口的类型,然后使用该接口中的 ComparesTo() 方法(IIRC 支持/处理空值),如下所示:
public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))
There are probably other interfaces that you could use as well IEquitable, etc.
您可能还可以使用其他接口以及 IEquitable 等。
回答by Lasse V. Karlsen
Try this:
尝试这个:
if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))
that should compile, and do what you want.
这应该编译,并做你想做的。
回答by cfeduke
@ilitirit:
@ilitirit:
public class Class<T> where T : IComparable
{
public T Value { get; set; }
public void MyMethod(T val)
{
if (Value == val)
return;
}
}
Operator '==' cannot be applied to operands of type 'T' and 'T'
运算符“==”不能应用于“T”和“T”类型的操作数
I can't think of a way to do this without the explicit null test followed by invoking the Equals method or object.Equals as suggested above.
如果没有显式空测试,然后调用上面建议的 Equals 方法或 object.Equals,我想不出一种方法来做到这一点。
You can devise a solution using System.Comparison but really that's going to end up with way more lines of code and increase complexity substantially.
您可以使用 System.Comparison 设计一个解决方案,但实际上最终会产生更多的代码行并显着增加复杂性。
回答by Damian Powell
I think you probably need to split this logic into two parts and check for null first.
我认为您可能需要将此逻辑分为两部分并首先检查 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;
}
In the IsNull method, we're relying on the fact that ValueType objects can't be null by definition so if value happens to be a class which derives from ValueType, we already know it's not null. On the other hand, if it's not a value type then we can just compare value cast to an object against null. We could avoid the check against ValueType by going straight to a cast to object, but that would mean that a value type would get boxed which is something we probably want to avoid since it implies that a new object is created on the heap.
在 IsNull 方法中,我们依赖于这样一个事实,即 ValueType 对象根据定义不能为 null,因此如果 value 恰好是从 ValueType 派生的类,我们已经知道它不是 null。另一方面,如果它不是值类型,那么我们可以将值转换为对象与 null 进行比较。我们可以通过直接转换为对象来避免对 ValueType 的检查,但这意味着值类型会被装箱,这是我们可能想要避免的,因为它意味着在堆上创建了一个新对象。
In the IsNullOrEmpty method, we're checking for the special case of a string. For all other types, we're comparing the value (which already know is notnull) against it's default value which for all reference types is null and for value types is usually some form of zero (if they're integral).
在 IsNullOrEmpty 方法中,我们检查字符串的特殊情况。对于所有其他类型,我们将值(已经知道不是空的)与其默认值进行比较,默认值对于所有引用类型都是空值,对于值类型通常是某种形式的零(如果它们是整数)。
Using these methods, the following code behaves as you might expect:
使用这些方法,以下代码的行为如您所料:
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) ...
}
回答by Joel Coehoorn
(Edited)
(已编辑)
Marc Gravell has the best answer, but I wanted to post a simple code snippet I worked up to demonstrate it. Just run this in a simple C# console app:
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();
}
One more thing: can someone with VS2008 try this as an extension method? I'm stuck with 2005 here and I'm curious to see if that would be allowed.
还有一件事:使用 VS2008 的人可以尝试将此作为扩展方法吗?我在这里停留在 2005 年,我很想知道是否允许这样做。
Edit:Here is how to get it working as an extension method:
编辑:以下是如何让它作为扩展方法工作:
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));
}
}
回答by Nick Farina
To handle all types of T, including where T is a primitive type, you'll need to compile in both methods of comparison:
要处理所有类型的 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;
}
回答by Reed Copsey
There is going to be a problem here -
这里会有问题——
If you're going to allow this to work for any type, default(T) will always be null for reference types, and 0 (or struct full of 0) for value types.
如果您打算允许它适用于任何类型,则 default(T) 对于引用类型将始终为 null,对于值类型将始终为 0(或满 0 的结构)。
This is probably not the behavior you're after, though. If you want this to work in a generic way, you probably need to use reflection to check the type of T, and handle value types different than reference types.
不过,这可能不是您所追求的行为。如果您希望它以通用方式工作,您可能需要使用反射来检查 T 的类型,并处理与引用类型不同的值类型。
Alternatively, you could put an interface constraint on this, and the interface could provide a way to check against the default of the class/struct.
或者,您可以对此设置接口约束,并且接口可以提供一种方法来检查类/结构的默认值。