0%

C Sharp 补课笔记 -- 变体泛型

基础知识普及
逆变与协变对应子类与父类之间的转换

变体泛型接口

使用 out 关键字将泛型类型参数声明为协变

类型仅用作接口方法的返回类型,不用作方法参数的类型

1
2
3
4
5
6
interface ICovariant<out R>
{
R GetSomething();
// The following statement generates a compiler error.
// void SetSomething(R sampleArg);
}

此规则有一个例外。 如果具有用作方法参数的逆变泛型委托,则可将类型用作该委托的泛型类型参数

1
2
3
4
interface ICovariant<out R>
{
void DoSomething(Action<R> callback);
}

类型不用作接口方法的泛型约束

1
2
3
4
5
6
7
interface ICovariant<out R>
{
// The following statement generates a compiler error
// because you can use only contravariant or invariant types
// in generic constraints.
// void DoSomething<T>() where T : R;
}

使用 in 关键字将泛型类型参数声明为逆变

逆变类型只能用作方法参数的类型,不能用作接口方法的返回类型。 逆变类型还可用于泛型约束

1
2
3
4
5
6
7
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}

可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数

1
2
3
4
5
6
7
i
nterface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}

实现变体泛型

在类中实现变体泛型接口时,所用语法和用于固定接口的语法相同

1
2
3
4
5
6
7
8
9
10
11
12
interface ICovariant<out R>
{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}

实现变体接口的类是固定类

1
2
3
4
5
6
7
8
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;
// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

扩展变体泛型接口

扩展变体泛型接口时,必须使用 in 和 out 关键字来显式指定派生接口是否支持变体。 编译器不会根据正在扩展
的接口来推断变体

1
2
3
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

尽管 IInvariant<T> 接口和 T 接口扩展的是同一个接口,但泛型类型参数 IExtCovariant<out T> 在前者中为固定参数,在后者中为协变参数。 此规则也适用于逆变泛型类型参数。
无论泛型类型参数 T 在接口中是协变还是逆变,都可以创建一个接口来扩展这两类接口,只要在扩展接口中,该T 泛型类型参数为固定参数

1
2
3
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

如果泛型类型参数 T 在一个接口中声明为协变,则无法在扩展接口中将其声明为逆变,反之亦然

1
2
3
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

变体泛型委托

NET Framework 3.5 引入了变体支持,用于在 C# 中匹配所有委托的方法签名和委托类型。 这表明不仅可以将具有匹配签名的方法分配给委托,还可以将返回派生程度较大的派生类型的方法分配给委托(协变),或者如果方法所接受参数的派生类型所具有的派生程度小于委托类型指定的程度(逆变),也可将其分配给委托。 这包括泛型委托和非泛型委托。

在 .NET Framework 4 或更高版本中,可以启用委托之间的隐式转换,以便在具有泛型类型参数所指定的不同类型按变体的要求继承自对方时,可以将这些类型的泛型委托分配给对方。
若要启用隐式转换,必须使用 in 或 out 关键字将委托中的泛型参数显式声明为协变或逆变。

1
2
3
4
5
6
7
8
9
// Type T is declared covariant by using the out keyword.
public delegate T SampleGenericDelegate <out T>();
public static void Test()
{
SampleGenericDelegate <String> dString = () => " ";
// You can assign delegates to each other,
// because the type T is declared covariant.
SampleGenericDelegate <Object> dObject = dString;
}

如果仅使用变体支持来匹配方法签名和委托类型,且不使用 in 和 out 关键字,则可能会发现有时可以使用相同的 lambda 表达式或方法实例化委托,但不能将一个委托分配给另一个委托

在以下代码示例中, SampleGenericDelegate<String> 不能显式转换为 SampleGenericDelegate<Object> ,尽管String 继承 Object 。 可以使用 T 关键字标记 泛型参数 out 解决此问题

1
2
3
4
5
6
7
8
9
10
11
12
13
public delegate T SampleGenericDelegate<T>();
public static void Test()
{
SampleGenericDelegate<String> dString = () => " ";
// You can assign the dObject delegate
// to the same lambda expression as dString delegate
// because of the variance support for
// matching method signatures with delegate types.
SampleGenericDelegate<Object> dObject = () => " ";
// The following statement generates a compiler error
// because the generic type T is not marked as covariant.
// SampleGenericDelegate <Object> dObject = dString;
}

可以使用 out 关键字声明泛型委托中的泛型类型参数协变。 协变类型只能用作方法返回类型,而不能用作方法参数的类型

public delegate R DCovariant<out R>();

可以使用 in 关键字声明泛型委托中的泛型类型参数逆变。 逆变类型只能用作方法参数的类型,而不能用作方法返回类型

public delegate void DContravariant<in A>(A a);