针对通用接口创建扩展方法还是作为通用约束?

时间:2013-05-08 21:43:09

标签: c# generics interface extension-methods generic-constraints

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

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>
    {
        // ...
    }
}

我认为这些操作几乎完全相同,但我不太确定......我在这里俯瞰什么?

4 个答案:

答案 0 :(得分:5)

是的。

第一个签名将匹配可与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进行了测试。

答案 1 :(得分:0)

如果我们稍微重写一下,使用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>
    {
        // ...
    }
}

所以不,他们根本不相同。

答案 2 :(得分:0)

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

根据Lee的反馈进行编辑:下面的内容看起来完全相同,但是虽然两者都需要值和其他值来实现IComparable,但第二个也要求它们可以分配给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>
{
    // ...
}

答案 3 :(得分:0)

差异非常明显。请注意,您必须在方法(泛型方法)或包含类(泛型类,扩展方法无法实现)中定义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将被加框,而value中的IFoo2将不会被加框。值类型是密封的,并且逆变量不适用于值类型,因此这是这种情况下最重要的区别。

使用参考类型:

对于引用类型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>可以省略,因为它会被推断出来,但为了清楚起见,我将其包括在内。