简体   繁体   English

Null 或 C# 中通用参数的默认比较

[英]Null or default comparison of generic argument in C#

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.但这不会编译,因为我不能保证 T 会实现 == 运算符。 So I switched the code to this:所以我将代码切换为:

if (myArgument.Equals(default(T)))

Now this compiles, but will fail if myArgument is null, which is part of what I'm testing for.现在可以编译,但如果 myArgument 是 null(这是我正在测试的一部分),则会失败。 I can add an explicit null check like this:我可以像这样添加一个明确的 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. ReSharper 甚至建议我将 myArgument == null 部分更改为我开始的 myArgument == default(T) 部分。 Is there a better way to solve this problem?有没有更好的方法来解决这个问题?

I need to support both references types and value types.我需要同时支持引用类型和值类型。

To avoid boxing, the best way to compare generics for equality is with EqualityComparer<T>.Default .为避免装箱,比较 generics 是否相等的最佳方法是使用EqualityComparer<T>.Default This respects IEquatable<T> (without boxing) as well as object.Equals , and handles all the Nullable<T> "lifted" nuances.这尊重IEquatable<T> (没有装箱)以及object.Equals ,并处理所有Nullable<T> “提升”的细微差别。 Hence:因此:

if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}

This will match:这将匹配:

  • null for classes null 用于类
  • null (empty) for Nullable<T> null(空)为Nullable<T>
  • zero/false/etc for other structs其他结构的零/假/等

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.使用static object.Equals()方法可以避免您自己进行null检查。 Explicitly qualifying the call with object.使用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调用前加上类型名称,只是为了使代码更易于溶解。

I was able to locate a Microsoft Connect article that 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.如果已知类型是引用类型,则在 object 上定义的默认重载会测试变量的引用相等性,尽管类型可以指定自己的自定义重载。 The compiler determines which overload to use based on the static type of the variable (the determination is not polymorphic).编译器根据变量的 static 类型确定使用哪个重载(确定不是多态的)。 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.编译器可以发出对 ValueType.Equals(Object) 的调用,但此方法使用反射并且与特定值比较相比效率很低。 Therefore, even if you were to specify a value-type constraint on T, there is nothing reasonable for the compiler to generate here:因此,即使您要在 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.在您介绍的情况下,编译器甚至不知道 T 是值类型还是引用类型,类似地,不会生成任何对所有可能类型都有效的内容。 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.引用比较对值类型无效,并且对于不重载的引用类型,某种值比较将是意外的。

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 的所有情况都源自已知的基础 class ,则可以使用泛型类型限制让编译器知道。

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方法。

Try this:尝试这个:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))

that should compile, and do what you want.那应该编译,并做你想做的事。

(Edited) (已编辑)

Marc Gravell has the best answer, but I wanted to post a simple code snippet I worked up to demonstrate it. Marc Gravell 给出了最好的答案,但我想发布一个简单的代码片段来演示它。 Just run this in a simple C# console app:只需在一个简单的 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?还有一件事:有 VS2008 的人可以试试这个作为扩展方法吗? I'm stuck with 2005 here and I'm curious to see if that would be allowed.我在这里坚持使用 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));
    }
}

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;
    }

Extension method based on accepted answer.基于已接受答案的扩展方法。

   public static bool IsDefault<T>(this T inObj)
   {
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Usage:用法:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue == null || tValue.IsDefault()) return false;
   }

Alternate with null to simplify:与 null 交替使用以简化:

   public static bool IsNullOrDefault<T>(this T inObj)
   {
       if (inObj == null) return true;
       return EqualityComparer<T>.Default.Equals(inObj, default);
   }

Usage:用法:

   private bool SomeMethod(){
       var tValue = GetMyObject<MyObjectType>();
       if (tValue.IsNullOrDefault()) return false;
   }

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.如果您要允许它适用于任何类型,则默认(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.或者,您可以对此设置接口约束,并且该接口可以提供一种方法来检查类/结构的默认值。

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.在 IsNull 方法中,我们依赖于 ValueType 对象不能是 null 的事实,因此如果 value 恰好是派生自 ValueType 的 class,我们已经知道它不是 Z37A6259CC0C1DAE299ZA786 On the other hand, if it's not a value type then we can just compare value cast to an object against null.另一方面,如果它不是值类型,那么我们可以将转换为 object 的值与 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.我们可以通过直接转换为 object 来避免对 ValueType 的检查,但这意味着值类型会被装箱,这是我们可能想要避免的,因为这意味着在堆上创建了一个新的 object。

In the IsNullOrEmpty method, we're checking for the special case of a string.在 IsNullOrEmpty 方法中,我们正在检查字符串的特殊情况。 For all other types, we're comparing the value (which already know is not null) 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).对于所有其他类型,我们将值(已经知道为空)与其默认值进行比较,对于所有引用类型,默认值是 null,对于值类型,通常是某种形式的零(如果它们是整数)。

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) ...
}

I use:我用:

public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}

Just a hacky answer and as a reminder for myself.只是一个骇人听闻的答案,并作为对自己的提醒。 But I find this quite helpful for my project.但我发现这对我的项目很有帮助。 The reason I write it like this is that because I don't want default integer 0 being marked as null if the value is 0我这样写的原因是因为我不希望默认 integer 0 被标记为 null 如果值为 0

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));
}

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 等接口的类型,然后使用该接口(IIRC 支持/处理空值)中的 ComparesTo() 方法,如下所示:

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 等。

@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.如果没有明确的 null 测试然后调用 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.您可以 devise 使用 System.Comparison 解决方案,但实际上这将导致更多的代码行并大大增加复杂性。

I think you were close.我想你很接近。

if (myArgument.Equals(default(T)))

Now this compiles, but will fail if myArgument is null, which is part of what I'm testing for.现在可以编译,但如果myArgument是 null(这是我正在测试的一部分),则会失败。 I can add an explicit null check like this:我可以像这样添加一个明确的 null 检查:

You just need to reverse the object on which the equals is being called for an elegant null-safe approach.您只需要反转 object ,在其上调用 equals 即可获得优雅的 null 安全方法。

default(T).Equals(myArgument);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM