草堂记事
保留火种,点烧激情!
看一个简单的例子。
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。
posted on 2010-03-02 23:11  叶天  阅读(979)  评论(0编辑  收藏  举报