针对泛型接口或作为泛型约束创建扩展方法
本文关键字:约束 创建 扩展 方法 泛型 泛型接口 | 更新日期: 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
,那不是同样的问题吗?
在这种情况下,可以清楚地看到IsFoo1
与IsFoo2
完全不同。
因为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>
,其中Y
与X
不同(并且无关)。
值类型:
对于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>
,因为它将被推断出来,但为了清晰起见,我在这里包含了它。