代表中的协变性,任何示例?
我正在阅读这篇msdn文章,反例(键盘和鼠标事件)是伟大而有用的,而协方差的例子(哺乳动物和狗)看起来并不如此。
键盘和鼠标事件非常好,因为您可以在多个案例中使用1个处理程序; 但我想不出赋予一个返回更多派生类型给返回基类型的处理程序的处理程序的好处,更不用说它有一个关心返回类型的委托不太常见?
有人可以提供一个更实际的代表协变的例子吗?
这是一个“真实世界”的例子,我实现了一个服务定位器。 我想创建一个类,我可以注册知道如何生成某种类型实例的“工厂”代理,然后再解析某些类型的实例。 以下是这样的内容:
interface ISomeService { ... }
class SomeService : ISomeService { ... }
class IocContainer
{
private readonly Dictionary<Type, Func<object>> _factories = new Dictionary<Type, Func<object>>();
public void Register<T>(Func<T> factory)
where T: class
{
_factories[typeof(T)] = factory;
}
// Note: this is C#6, refactor if not using VS2015
public T Resolve<T>() => (T)_factories[typeof(T)]();
}
class Program
{
static void Main(string[] args)
{
var container = new IocContainer();
container.Register<ISomeService>(() => new SomeService());
// ...
var service = container.Resolve<ISomeService>();
}
}
现在,我正在做container.Register<ISomeService>(() => new SomeService())
, Func
委托的协方差在两个层次上发挥作用:
Func<SomeService>
并且可以在Func<ISomeService>
被指定,而且没有问题,因为SomeService
是一个ISomeService
Register
方法中,可以在Func<object>
被预期没有问题的地方分配一个Func<T>
,因为任何引用类型都是一个object
如果Func<SomeService>
不能分配给Func<ISomeService>
,或者Func<ISomeService>
不能分配给Func<object>
(通过协变),那么这个例子将不起作用。
你是对的,这是不常见的事情返回一个价值,这是有点约定(实际上它不仅仅是约定:看额外阅读#2)。 然而,代表不仅仅是事件,它们基本上是近半个世纪的C风格“指向函数的指针”(C术语)的.NET版本。
为了让控制流概念中的事件和委托和监听器(java)留下神秘的面纱,他们只是简单的回调,所以如果他们具有返回值是完全合理的。
所以从这个回调角度来看:假设我想处理动物。 我想使用一个函数指针(我的意思是:委托或lambda)来做这个处理的一部分。 让我们称之为FeedAnimal。 我想要一个其他的骨架方法,它调用这种饲料方法,让我们称之为CareAnimal。 我想用运行时变量模式将喂养算法插入到护理算法中,因此Care将具有一个委托参数:feed。 喂养后饲料方法返回动物。
现在的观点:Feed和Dog都有不同的实现,一个返回Dog,另一个返回Cat .... Care()方法接受一个委托参数,返回Animal。
[额外阅读#1]:这种多态实现不是OOP多态实现。 在OOP中,可以通过虚拟方法重载实现类似。
[额外阅读#2]:代表(和事件)他们是多播委托的真正令人不安的事情我的意思是一个委托(默认情况下是多播委托)可以包含许多方法入口点。 当它被调用时,所有包含的方法都以非特定顺序在一个循环中调用。 但是,如果签名不是无效的,将会有一个返回值。 当然这是令人困惑的,所以我们平安地说:如果我们使用委托(或事件)的多播功能,那么它就没有任何意义而不是无效返回。 事件通常是组播,这来自发布者/订阅者DP隐喻:许多订阅者(处理程序)可以订阅(+ =)到发布者出版物而不知道彼此的任何信息。
那么,如果你看看Func<T, TResult>
Delegate的声明。
public delegate TResult Func<in T, out TResult>(
T arg
)
您可以看到输入参数的类型是逆变的,但结果或返回值的类型是协变的。
熟悉的Linq扩展Select
,有一个接受这个委托的重载。
此外,请注意Select
的返回类型是IEnumerable<T>
,它是一个协变接口,即
public IEnumerable<out T>
{
...
}
现在考虑类型,
abstract class Mammal
{
}
和
class Dog : Mammal
{
}
我可以声明委托的一个实例。
var doItToMammals = new Func<Mammal, Mammal>(mammal => mammal);
我可以通过这个委托来Select
没有差异。
IEnumerable<Mammal> mammals = new List<Mammal>().Select(doItToMammals);
现在,因为函数的输入是逆变的,我可以做
IEnumerable<Mammal> mammals = new List<Dog>().Select(doItToMammals);
现在是这个观点 ,因为结果是协变的,我可以做
IEnumerable<Dogs> dogs = new List<Dog>().Select<Dog, Dog>(doItToMammals);
链接地址: http://www.djcxy.com/p/87355.html