看一个简单的例子。
IEnumerable<string> a = new List<string> { "a", "b" };
IEnumerable<object> b = a;
这在 4.0 以前的版本中是不允许的。尽管 string 继承自 object,但 IEnumerable<string> 和 IEnumerable<object> 之间并无继承关系,泛型类型与泛型参数是两码事。可在实际开发中,类似上面的转换是很常见的。
1. Covariance
泛型协变规则:
- 泛型参数受 out 关键字约束,只能用于属性或委托返回值。
- 隐式转换目标的泛型参数类型必须是当前类型的 "基类"。
out 约束示例:
interface ITest<out T>
{
T X
{
get; // Allowed!
set; // Not Allowed!
}
T M(T x); // Return Allowed! Parameter Not Allowed!
}
我们写一个支持协变的接口示例。
interface ITest<out T>
{
T X { get; }
T M();
}
class TestClass<T> : ITest<T>
where T : Base, new()
{
public T X { get; set; }
public T M()
{
return new T();
}
}
class Base { }
class Derived : Base { }
class Program
{
static void Main(string[] args)
{
ITest<Derived> _derived = new TestClass<Derived> { X = new Derived() };
ITest<Base> _base = _derived; // Covariance
Base x = _base.X;
Base m = _base.M();
}
}
这个例子虽然简单,但它很好地说明了协变的规则。受 out 约束,泛型参数只能用于返回值,而这些派生类型(Derived)的返回值总是能隐式转换为基类型(Base),因此上面例子协变隐式装换后,再访问属性 X 和 方法 M 是不会存在任何问题的。
.NET 4.0 Framework 已经对很多泛型集合接口做了协变处理,包括 IEnumerable<T>, IEnumerator<T>, IQueryable<T>, IGrouping<TKey, TElement> 等。
namespace System.Collections.Generic
{
// Summary:
// Exposes the enumerator, which supports a simple iteration over a collection
// of a specified type.
//
// Type parameters:
// T:
// The type of objects to enumerate.This type parameter is covariant. That is,
// you can use either the type you specified or any type that is more derived.
// For more information about covariance and contravariance, see Covariance
// and Contravariance in Generics.
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
}
如此,我们才有了本文刚开始的那个集合的隐式转换演示。我们给一个泛型委托协变的示例。
class Base
{
public int X { get; set; }
}
class Derived : Base
{
}
delegate T TestHandler<out T>(int x);
class Program
{
static void Main(string[] args)
{
TestHandler<Derived> _derived = (x) => new Derived { X = x };
TestHandler<Base> _base = _derived; // Covariance
Base o = _base(123);
}
}
同样,Framework 4.0 中也为常用的泛型委托做了一些准备。
namespace System
{
// Summary:
// Encapsulates a method that has no parameters and returns a value of the type
// specified by the TResult parameter.
//
// Type parameters:
// TResult:
// The type of the return value of the method that this delegate encapsulates.This
// type parameter is covariant. That is, you can use either the type you specified
// or any type that is more derived. For more information about covariance and
// contravariance, see Covariance and Contravariance in Generics.
//
// Returns:
// The return value of the method that this delegate encapsulates.
public delegate TResult Func<out TResult>();
}
2. Contravariance
泛型逆变规则:
- 泛型参数受 in 关键字约束,只能用于属性设置或委托(方法)参数。
- 隐式转换目标的泛型参数类型必须是当前类型的 "继承类"。
in 约束示例:
interface ITest<in T>
{
T X
{
get; // Not Allowed!
set; // Allowed!
}
T M(T o); // Return Not Allowed! Parameter Allowed!
}
逆变初看上去有些别扭。因为我们试图将 "基类" 隐式转换为 "继承类"。泛型逆变主要是为泛型委托准备的,我们看一个例子就明白了。
class Base { }
class Derived : Base { }
class Program
{
static void Main(string[] args)
{
Action<Base> _base = (o) => Console.WriteLine(o);
Action<Derived> _derived = _base;
_derived(new Derived());
}
}
逆变将 Action<Base> 隐式转换为 Action<Derived>,这正好和协变相反,从泛型参数 "继承关系" 上来说这有点不可理解。但当我们调用转换后的方法 "_derived(derivedObj)" 时会发现所提供的方法参数总是原方法的 "继承类",因此这种调用总是符合规则的。回忆一下 C# 2.0/3.0 中有关委托逆变的定义,其实是完全一致的。
namespace System
{
// Summary:
// Encapsulates a method that has a single parameter and does not return a value.
//
// Parameters:
// obj:
// The parameter of the method that this delegate encapsulates.
//
// Type parameters:
// T:
// The type of the parameter of the method that this delegate encapsulates.This
// type parameter is contravariant. That is, you can use either the type you
// specified or any type that is less derived. For more information about covariance
// and contravariance, see Covariance and Contravariance in Generics.
public delegate void Action<in T>(T obj);
}
泛型委托逆变示例 2
class Base
{
public int X { get; set; }
}
class Derived : Base
{
}
delegate void TestHandler<in T>(T o);
class Program
{
static void Main(string[] args)
{
TestHandler<Base> _base = (o) => Console.WriteLine(o.X);
TestHandler<Derived> _derived = _base;
_derived(new Derived());
}
}
泛型接口逆变示例
interface ITest<in T>
{
void M(T o);
}
class TestClass<T> : ITest<T>
where T : Base
{
public void M(T o)
{
Console.WriteLine(o.X);
}
}
class Base
{
public int X { get; set; }
}
class Derived : Base
{
}
class Program
{
static void Main(string[] args)
{
ITest<Base> _base = new TestClass<Base>();
ITest<Derived> _derived = _base;
_derived.M(new Derived { X = 12345 });
}
}
和委托一样,泛型接口的逆变同样是在 in 的约束下为原本 Base 类型的参数提供 Derived 对象,因此隐式转换没有什么问题。
Func<Derived> _derived1 = () => new Derived();
Func<Base> _base1 = _derived1; // Covariance: [out] Derived -> Base
Base obj = _base1();
Action<Base> _base2 = (o) => { };
Action<Derived> _derived2 = _base2; // Contravariant: [in] Base -> Derived
_derived2(new Derived());
Func<Base, Derived> _source = (o) => new Derived();
Func<Derived, Base> _target = _source; // Covariant return, Contravariant parameter
Base obj2 = _target(new Derived());
除上述规则外,泛型协变和逆变还须遵循如下规则:
- In the .NET Framework version 4 Beta 2, variant type parameters are restricted to generic interface and generic delegate types.
- A generic interface or generic delegate type can have both covariant and contravariant type parameters.
- Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.
- Variance does not apply to delegate combination. That is, given two delegates of types Action<Derived> and Action<Base>, you cannot combine the second delegate with the first although the result would be type safe. Variance allows the second delegate to be assigned to a variable of type Action<Derived>, but delegates can combine only if their types match exactly.
相关细节可参考 MSDN。