<转>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。
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。
相关文章推荐
- <转>C# 4.0 为泛型编程引入了 协变 和 逆变 支持,这是个不错的福利,能省掉以往的一些麻烦。不过当前(Beta2)仅支持泛型接口和泛型委托。
- C#中泛型方法与泛型接口 C#泛型接口 List<IAll> arssr = new List<IAll>(); interface IPerson<T> c# List<接口>小技巧 泛型接口协变逆变的几个问题
- C#里泛型接口支持协变、逆变和不支持协变、逆变的对比?
- c#打包文件解压缩 C#中使用委托、接口、匿名方法、泛型委托实现加减乘除算法 一个简单例子理解C#的协变和逆变 对于过长字符串的大小比对
- <C#> 泛型、委托和一些易混淆的定义(1)
- c#中泛型的协变与逆变:<in T>详解
- 编写高质量代码改善C#程序的157个建议——建议43:让接口中的泛型参数支持协变
- C#深入学习:泛型修饰符in,out、逆变委托类型和协变委托类型
- 第五节:泛型(泛型类、接口、方法、委托、泛型约束、泛型缓存、逆变和协变)
- c#4.0泛型接口和泛型委托的协变和逆变
- 理解 C# 泛型接口中的协变与逆变(抗变)
- [译]委托和接口泛型参数类型的协变和逆变
- <转>C# 中的委托和事件
- 委托中的协变和逆变(C# 编程指南)
- C# 4.0新特性-"协变"与"逆变"以及背后的编程思想
- C# Unity 对于泛型接口的支持
- C# 语言特性系列(1) 委托 - 协变和逆变
- .NET 4.0中的泛型的协变和逆变
- C# 泛型的协变和逆变
- [转]C#4.0中的协变和逆变