当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《C#中泛型参数的空或默认比较》经验,为你挑选了6个好方法。

我有一个像这样定义的泛型方法:

public void MyMethod(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),这是我开始的地方.有没有更好的方法来解决这个问题?

我需要支持两种引用类型和值类型.



1> Marc Gravell..:

为了避免装箱,比较泛型的最佳方法是使用EqualityComparer.Default.这尊重IEquatable(没有拳击)以及object.Equals处理所有Nullable"提升"的细微差别.因此:

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

这将匹配:

类为null

null(空)for Nullable

其他结构的零/假/等


哇,多么令人愉快的晦涩难懂!这绝对是可行的方式,不仅如此.
很棒的答案!更好的是为这行代码添加扩展方法,以便你可以去obj.IsDefaultForType()
我无法确定这个答案是否会让我远离或接近精神错乱.+1
在'Person`的情况下@nawfal,`p1.Equals(p2)`将取决于它是否在公共API上实现`IEquatable `,或者通过显式实现 - 即编译器是否可以看到公共`Equals(人其他)`方法.然而; 在*generics*中,相同的IL用于所有`T`; 碰巧实现`IEquatable `的`T1`需要被处理成与没有的`T2`相同 - 所以不,它不会**发现`Equals(T1 other)`方法,甚至如果它在运行时存在.在这两种情况下,还有"null"要考虑(对象).因此,对于泛型,我会使用我发布的代码.

2> Kent Boogaar..:

这个怎么样:

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

使用该static object.Equals()方法可以避免您自己进行null检查.object.根据您的上下文,可能没有必要显式地限定调用,但我通常static使用类型名称为调用添加前缀,以使代码更易于解析.


是的,它通常是,但可能不依赖于上下文.可能有一个实例Equals()方法,它接受两个参数.我倾向于使用类名显式地为所有静态调用添加前缀,如果只是为了使代码更容易阅读.
需要注意的是,它会导致拳击,在某些情况下,它可能很重要
您甚至可以删除“对象”。部分,因为它是多余的。如果(Equals(myArgument,default(T)))
对我来说,当使用已经装箱的整数时,这不起作用。因为它将成为一个对象,而object的默认值为null而不是0。

3> Eric Schoono..:

我能够找到一篇详细讨论此问题的Microsoft Connect文章:

不幸的是,这种行为是设计上的,并没有一个简单的解决方案来启用可能包含值类型的类型参数.

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

public class Test where T : Exception

如果已知类型是值类型,则根据使用的确切类型执行特定值相等性测试.这里没有好的"默认"比较,因为参考比较对值类型没有意义,并且编译器无法知道要发出哪个特定值比较.编译器可以发出对ValueType.Equals(Object)的调用,但是此方法使用反射,并且与特定值比较相比效率很低.因此,即使您要在T上指定值类型约束,编译器也无法在此处生成:

public class Test where T : struct

在您提供的情况下,编译器甚至不知道T是值还是引用类型,同样没有任何生成可以对所有可能类型有效的内容.参考比较对于值类型无效,对于不重载的引用类型,某种值比较会出乎意料.

这是你可以做的......

我已经验证了这两种方法都可以用于参考和值类型的通用比较:

object.Equals(param, default(T))

要么

EqualityComparer.Default.Equals(param, default(T))

要与"=="运算符进行比较,您需要使用以下方法之一:

如果T的所有情况都来自已知的基类,您可以让编译器知道使用泛型类型限制.

public void MyMethod(T myArgument) where T : MyBase

编译器然后识别如何执行操作,MyBase并且不会抛出"运算符'=='不能应用于现在看到的'T'和'T'类型的操作数"错误.

另一种选择是将T限制为任何实现的类型IComparable.

public void MyMethod(T myArgument) where T : IComparable

然后使用IComparable接口CompareTo定义的方法.


"这种行为是设计使然,没有一个简单的解决方案可以使用可能包含值类型的类型参数." 其实微软错了.有一个简单的解决方案:MS应该扩展ceq操作码以作为按位运算符在值类型上运行.然后他们可以提供一个简单地使用这个操作码的内在函数,例如object.BitwiseOrReferenceEquals (值,默认(T)),它只使用ceq.对于值和引用类型,这将检查值*的按位相等*(但对于引用类型,引用按位相等与object.ReferenceEquals相同)

4> angry person..:

试试这个:

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

应该编译,并做你想要的.


1)你有没有尝试过,2)你比较对象的对象是什么?`IEqualityComparer`的`Equals`方法有两个参数,两个要比较的对象,所以不,它不是多余的.

5> Joel Coehoor..:

(编辑)的

Marc Gravell有最好的答案,但是我想发布一个简单的代码片段,我用它来演示它.只需在一个简单的C#控制台应用程序中运行它:

public static class TypeHelper
{
    public static bool IsDefault(T val)
    {
         return EqualityComparer.Default.Equals(obj,default(T));
    }
}

static void Main(string[] args)
{
    // value type
    Console.WriteLine(TypeHelper.IsDefault(1)); //False
    Console.WriteLine(TypeHelper.IsDefault(0)); // True

    // reference type
    Console.WriteLine(TypeHelper.IsDefault("test")); //False
    Console.WriteLine(TypeHelper.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(this T val)
    {
        return EqualityComparer.Default.Equals(val, default(T));
    }
}


它确实"工作"作为扩展方法.这很有意思,因为即使你在o为空时你说o.IsDefault ()它也有效.吓人=)

6> Nick Farina..:

要处理所有类型的T,包括T是基本类型,您需要在两种比较方法中进行编译:

    T Get(Func createObject)
    {
        T obj = createObject();
        if (obj == null || obj.Equals(default(T)))
            return obj;

        // .. do a bunch of stuff
        return obj;
    }


FYI:如果T变成值类型,那么与null的比较将被抖动视为始终为false.
有关不涉及装箱等的替代方案,请参阅EqualityComparer 答案
推荐阅读
oDavid_仔o_880
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有