您的位置:首页 > 编程语言 > C#

<转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。

2010-03-02 23:11 766 查看
看一个简单的例子。

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐