C# C中泛型参数的Null或默认比较#

C# C中泛型参数的Null或默认比较#,c#,generics,C#,Generics,我有一个定义如下的通用方法: public void MyMethod<T>(T myArgument) if (myArgument == default(T)) if (myArgument == null || myArgument.Equals(default(T))) public void MyMethod<T>(T myArgument) where T : IComparable ... if (0 == myArgument.ComparesTo(

我有一个定义如下的通用方法:

public void MyMethod<T>(T myArgument)
if (myArgument == default(T))
if (myArgument == null || myArgument.Equals(default(T)))
public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))
但这不会编译,因为我没有保证t将实现==运算符。所以我把代码换成了这个:

if (myArgument.Equals(default(T)))
现在可以编译了,但如果myArgument为null,则会失败,这是我测试的一部分。我可以像这样添加显式空检查:

public void MyMethod<T>(T myArgument)
if (myArgument == default(T))
if (myArgument == null || myArgument.Equals(default(T)))
public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))
现在我觉得这是多余的。ReSharper甚至建议我将myArgument==null部分更改为myArgument==default(T),这是我开始的地方。有没有更好的办法来解决这个问题

我需要同时支持引用类型和值类型。

我找到了一个详细讨论此问题的:


不幸的是,这种行为是经过设计的,并且没有一个简单的解决方案来支持使用可能包含值类型的类型参数

如果已知类型是引用类型,则对象上定义的默认重载将测试变量的引用相等性,尽管类型可以指定自己的自定义重载。编译器根据变量的静态类型确定要使用的重载(该确定不是多态的)。因此,如果更改示例以将泛型类型参数T约束为非密封引用类型(例如Exception),编译器可以确定要使用的特定重载,并编译以下代码:

然后使用

定义的
CompareTo
方法如何:

if (object.Equals(myArgument, default(T)))
{
    //...
}

使用
static object.Equals()
方法可以避免执行
null
检查。使用
对象显式限定调用。
可能不需要,这取决于您的上下文,但我通常使用类型名作为
静态
调用的前缀,只是为了使代码更易于理解。

不知道这是否符合您的要求,但您可以将T约束为实现IComparable等接口的类型,然后使用该接口(IIRC支持/处理null)中的ComparesTo()方法,如下所示:

public void MyMethod<T>(T myArgument)
if (myArgument == default(T))
if (myArgument == null || myArgument.Equals(default(T)))
public void MyMethod<T>(T myArgument) where T : IComparable
...
if (0 == myArgument.ComparesTo(default(T)))
public void MyMethod(T myArgument),其中T:i可比较
...
如果(0==myArgument.ComparesTo(默认值(T)))
您可能还可以使用IEquitable等其他接口。

尝试以下方法:

if (EqualityComparer<T>.Default.Equals(myArgument, default(T)))
if(EqualityComparer.Default.Equals(myArgument,Default(T)))
它应该编译并执行您想要的操作。

@ilitirit:

public class Class<T> where T : IComparable
{
    public T Value { get; set; }
    public void MyMethod(T val)
    {
        if (Value == val)
            return;
    }
}
公共类,其中T:i可比较
{
公共T值{get;set;}
公共无效方法(T val)
{
如果(值==val)
返回;
}
}
运算符“==”不能应用于“T”和“T”类型的操作数

如果没有显式的null测试,然后调用上面建议的Equals方法或object.Equals,我想不出一种方法来实现这一点


您可以使用System.Comparison设计一个解决方案,但实际上,这将导致更多的代码行,并大大增加复杂性。

我认为您可能需要将此逻辑分为两部分,并首先检查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;
}
公共静态bool为空或空(T值)
{
if(IsNull(值))
{
返回true;
}
if(值为字符串)
{
返回字符串.IsNullOrEmpty(值为字符串);
}
返回值。等于(默认值(T));
}
公共静态布尔为空(T值)
{
如果(值为ValueType)
{
返回false;
}
返回null==(对象)值;
}
在IsNull方法中,我们依赖于ValueType对象不能定义为null这一事实,因此如果value碰巧是从ValueType派生的类,我们已经知道它不是null。另一方面,如果它不是值类型,那么我们可以将值转换为对象与null进行比较。我们可以通过直接转到cast to对象来避免对ValueType的检查,但这意味着值类型将被装箱,这可能是我们想要避免的,因为这意味着在堆上创建了一个新对象

在IsNullOrEmpty方法中,我们检查字符串的特殊情况。对于所有其他类型,我们将值(已知值不是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) ...
}
类程序
{
公共类MyClass
{
公共字符串MyString{get;set;}
}
静态void Main()
{
int i1=1;测试(“i1”,i1);//错误
int i2=0;测试(“i2”,i2);//真
int?i3=2;测试(“i3”,i3);//错误
int?i4=null;测试(“i4”,i4);//真
Console.WriteLine();
字符串s1=“hello”测试(“s1”,s1);//False
字符串s2=null;测试(“s2”,s2);//真
string s3=string.Empty;Test(“s3”,s3);//True
字符串s4=“”;测试(“s4”,s4);//真
Console.WriteLine();
MyClass mc1=新建MyClass();测试(“mc1”,mc1);//错误
MyClass mc2=null;测试(“mc2”,mc2);//真
}
公共静态无效测试(字符串字段名,T字段)
{
Console.WriteLine(fieldName+”:“+IsNullOrEmpty(field));
}
//公共静态布尔值为NullOrEmpty(T值)。。。
//公共静态布尔值为空(T值)。。。
}
(已编辑)

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();
}
公共静态类TypeHelper
{
公共静态布尔值默认值(T val)
{
返回EqualityComparer.Default.Equals(obj,Default(T));
}
}
静态void Main(字符串[]参数)
{
//值类型
Console.WriteLine(TypeHelper.IsDefault(1));//False
Console.WriteLine(TypeHelper.IsDefault(0));//True
//参考类型
Console.WriteLine(TypeHelper.IsDefault(“te
if(EqualityComparer<T>.Default.Equals(obj, default(T))) {
    return obj;
}
if (myArgument.Equals(default(T)))
default(T).Equals(myArgument);
public class MyClass<T>
{
  private bool IsNull() 
  {
    var nullable = Nullable.GetUnderlyingType(typeof(T)) != null;
    return nullable ? EqualityComparer<T>.Default.Equals(Value, default(T)) : false;
  }
}
   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;
   }
   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;
   }
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));
}