Effective C# 学习笔记(二十九)在范型中的协变和逆变
2011-07-17 17:34 小郝(Kaibo Hao) 阅读(490) 评论(0) 编辑 收藏 举报协变:若一个返回值类型可以用该类型的子类型(继承类型)替代,我们就说该类型是可协变的
逆变:若一个参数的类型可以用该类型的父类型(被继承类型)替代,我们就说该类型是被逆变的。
在你的范型声明、使用中,注意支持协变(Covariance)和逆变(Contravariance)。编译器会捕捉你使用不当的逆变或协变行为。
举例说明协变和逆变:
这里有个抽象类CelestialBody,有Planet、Moon、Asteroid三个类型继承自该基类:
abstract public class CelestialBody
{
public double Mass { get; set; }
public string Name { get; set; }
// elided
}
public class Planet : CelestialBody
{
// elided
}
public class Moon : CelestialBody
{
// elided
}
public class Asteroid : CelestialBody
{
// elided
}
//下面这个方法以协变的方式来处理CeletialBody数组
public static void CoVariantArray(CelestialBody[] baseItems)
{
foreach (var thing in baseItems)
Console.WriteLine("{0} has a mass of {1} Kg",
thing.Name, thing.Mass);
}
//但下面这两种赋值操作是不安全的,编译可通过,但在运行时会抛出异常System.ArrayTypeMismatchException: 尝试访问类型与数组不兼容的元
素。
public static void UnsafeVariantArray(
CelestialBody[] baseItems)
{
baseItems[0] = new Asteroid { Name = "Hygiea", Mass = 8.85e19 };
}
CelestialBody[] spaceJunk = new Asteroid[5];
spaceJunk[0] = new Planet();
当范型这个特性引入C#中时,其对协变逆变的支持的很苛刻的。直到C# 4.0版本才添加了新的关键字(协变out 和逆变 in)来修饰范型接口的类型参数,才使这些范型可以更好的支持协变、逆变。
让我们看一个例子:
public static void CoVariantGeneric(
IEnumerable<CelestialBody> baseItems)
{
foreach (var thing in baseItems)
Console.WriteLine("{0} has a mass of {1} Kg",
thing.Name, thing.Mass);
}
上面这段代码用IEnumerable<T>作为形参类型,其可以用List<Planet>类型的对象作为参数。这是因为IEnumerable<T>利用了out关键字来限制T类型的使用范围。即类型IEnumberable<T>类型只能被用来作为返回值、属性的get访问器即部分代理的参数类型。IEnumberable<T>定义代码如下:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> :
IDisposable, IEnumerator
{
T Current { get; }
// MoveNext(), Reset() inherited from IEnumerator
}
适用范围:
协变:方法的返回值的类型,属性的get访问器的类型、部分代理中的参数类型
逆变:方法参数的类型,属性的set访问器的类型,部分代理中的参数类型
之前说的部分代理中的参数类型,其规范由BCL定义如下(这里只列出了部分重载):
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, T2, out TResult>(T1 arg1,T2 arg2);
public delegate void Action<in T>(T arg);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
public delegate void Action<in T1, in T2, T3>(T1 arg1,T2 arg2, T3 arg3);
另外注意协变、逆变的声明上下文:
//协变接口
public interface ICovariantDelegates<out T>
{
T GetAnItem();
Func<T> GetAnItemLater();
void GiveAnItemLater(Action<T> whatToDo);
}
在ICovariantDelegates<out T>接口中,T类型被声明为协变类型,而在该接口的
GiveAnItemLater方法中 Action<T>作为了该方法的参数,此时的T应为in 修饰的逆变类型参数,但这里的T是和上下文相关的,它只对ICovariantDelegates接口负责,也就是说尽管Action<in T>被声明为逆变的,T在GiveAnItemLater中的Action<T>声明是合法的。下例中的ActOnAnItemLater也是如此。
//逆变接口
public interface IContravariantDelegates<in T>
{
void ActOnAnItem(T item);
void GetAnItemLater(Func<T> item);
Action<T> ActOnAnItemLater();
}
出处:http://www.cnblogs.com/haokaibo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。