针对泛型接口或作为泛型约束创建扩展方法

本文关键字:约束 创建 扩展 方法 泛型 泛型接口 | 更新日期: 2023-09-27 18:12:50

我真的不确定这两个签名是否有任何真正的区别:

public static class MyCustomExtensions
{
    public static bool IsFoo(this IComparable<T> value, T other)
        where T : IComparable<T>
    {
        // ...
    }
    public static bool IsFoo(this T value, T other)
        where T : IComparable<T>
    {
        // ...
    }
}

我认为这些基本上是一样的,但我不太确定…我在这里忽略了什么?

针对泛型接口或作为泛型约束创建扩展方法

有。

第一个签名将匹配任何可以与T比较的类型,而不仅仅是T值。因此,任何实现IComparable<int>的类型都可以被第一个签名使用,而不仅仅是int

的例子:

void Main()
{
    10.IsFoo(20).Dump();
    new Dummy().IsFoo(20).Dump();
    IComparable<int> x = 10;
    x.IsFoo(20).Dump();
    IComparable<int> y = new Dummy();
    y.IsFoo(20).Dump();
}
public class Dummy : IComparable<int>
{
    public int CompareTo(int other)
    {
        return 0;
    }
}
public static class Extensions
{
    public static bool IsFoo<T>(this IComparable<T> value, T other)
        where T : IComparable<T>
    {
        Debug.WriteLine("1");
        return false;
    }
    public static bool IsFoo<T>(this T value, T other)
        where T : IComparable<T>
    {
        Debug.WriteLine("2");
        return false;
    }
}

将输出:

2
False
1
False
1
False
1
False

我用LINQPad测试了这个

如果我们稍微重写一下,使用IList而不是IComparable,那不是同样的问题吗?

在这种情况下,可以清楚地看到IsFoo1IsFoo2完全不同。
因为IsFoo1接受IList<IList<T>>的第一个参数
IsFoo2只接受IList<T>的第一个参数

public static class MyCustomExtensions
{
    public static bool IsFoo1(IList<T> value, T other)
        where T : IList<T>
    {
        // ...
    }
    public static bool IsFoo2(T value, T other)
        where T : IList<T>
    {
        // ...
    }
}

所以它们根本不一样

它们不完全相同。在第一个中,您将IComparable<T>传递给第一个而不是第二个,因此您的实际类型将是<IComparable<IComparable<T>>IComparable<T>

根据Lee的反馈编辑:下面这些看起来是一样的,但是它们都需要value和other来实现iccomparable,第二个还需要它们可分配给t

public static bool IsFoo<T>(IComparable<T> value, IComparable<T> other)
{
    // ...
}
public static bool IsFoo<T>(T value, T other)
    where T : IComparable<T>
{
    // ...
}

差别非常明显。请注意,您必须在方法(泛型方法)或包含类(泛型类,不可能使用扩展方法)中定义T。下面我调用两个方法1和2:

public static bool IsFoo1<T>(this IComparable<T> value, T other)
    where T : IComparable<T>
{
    return true;
}
public static bool IsFoo2<T>(this T value, T other)
    where T : IComparable<T>
{
    return true;
}

不同的是T是值类型还是引用类型。您可以通过使用约束where T : struct, IComparable<T>where T : class, IComparable<T>来限制。

一般使用任何类型T:某些疯狂的类型X可能被声明为IComparable<Y>,其中YX不同(并且无关)。

值类型:

对于IFoo1,第一个参数value将被装箱,而IFoo2中的value将不被装箱。值类型是密封的,并且逆变不适用于值类型,因此这是本例中最重要的区别。

引用类型:

对于引用类型T,装箱不是问题。但请注意,IComparable<>在其类型参数中是逆变的("in")。如果一些非密封类实现了IComparable<>,这一点很重要。我使用了这两个类:

class C : IComparable<C>
{
    public int CompareTo(C other)
    {
        return 0;
    }
}
class D : C
{
}

使用它们,下列调用是可能的,其中一些是由于继承和/或逆变:

        // IsFoo1
        new C().IsFoo1<C>(new C());
        new C().IsFoo1<C>(new D());
        new D().IsFoo1<C>(new C());
        new D().IsFoo1<C>(new D());
        new C().IsFoo1<D>(new D());
        new D().IsFoo1<D>(new D());
        // IsFoo2
        new C().IsFoo2<C>(new C());
        new C().IsFoo2<C>(new D());
        new D().IsFoo2<C>(new C());
        new D().IsFoo2<C>(new D());
        //new C().IsFoo2<D>(new D()); // ILLEGAL
        new D().IsFoo2<D>(new D());

当然,在许多情况下,可以省略通用参数<C>,因为它将被推断出来,但为了清晰起见,我在这里包含了它。