我没有参加PDC 2008,但我听到一些消息称C#4.0被宣布支持Generic协方差和反差异.也就是说,List<string>可以分配给List<object>.怎么会这样?
在Jon Skeet的C#深度书中,解释了为什么C#泛型不支持协方差和反方差.它主要用于编写安全代码.现在,C#4.0改为支持它们.它会带来混乱吗?
有人知道有关C#4.0的细节可以给出一些解释吗?
拿这个小LINQPad示例:
void Main()
{
Foo<object> foo = new Foo<string>();
Console.WriteLine(foo.Get());
}
class Foo<out T>
{
public T Get()
{
return default(T);
}
}
Run Code Online (Sandbox Code Playgroud)
它无法使用此错误进行编译:
方差修饰符无效.只能将接口和委托类型参数指定为变量.
我没有看到代码的任何逻辑问题.一切都可以静态验证.为什么不允许这样做?它是否会导致语言不一致,或者由于CLR的限制而被认为实施起来太昂贵了?如果是后者,我作为开发人员应该知道什么是上述限制?
考虑到接口支持它,我希望从逻辑上遵循该类支持.
首先,抱歉模糊的问题标题.我无法想出更精确的一个.
鉴于以下类型:
{ TCommand : ICommand }
«interface» «interface» /
+-----------+ +----------------------/----+
| ICommand | | ICommandHandler<TCommand> |
+-----------+ +---------------------------+
^ | Handle(command: TCommand) |
| +---------------------------+
| ^
| |
+------------+ +-------------------+
| FooCommand | | FooCommandHandler |
+------------+ +-------------------+
^
|
+-------------------+
| SpecialFooCommand |
+-------------------+
Run Code Online (Sandbox Code Playgroud)
我想编写一个Dispatch接受任何命令并将其发送到适当的方法ICommandHandler<>.我认为使用DI容器(Autofac)可能会大大简化从命令类型到命令处理程序的映射:
void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
{
var handler = autofacContainer.Resolve<ICommandHandler<TCommand>>();
handler.Handle(command);
}
Run Code Online (Sandbox Code Playgroud)
假设DI容器知道上面显示的所有类型.现在我打电话给:
Dispatch(new SpecialFooCommand(…));
Run Code Online (Sandbox Code Playgroud)
实际上,这将导致Autofac抛出一个ComponentNotRegisteredException,因为没有ICommandHandler<SpecialFooCommand>可用的.
然而,理想情况下,我仍然希望SpecialFooCommand由最接近匹配的命令处理程序处理,即.通过FooCommandHandler在上面的例子. …
C#4.0中的通用差异已经实现,可以在没有异常的情况下编写以下内容(这将在C#3.0中发生):
List<int> intList = new List<int>();
List<object> objectList = intList;
Run Code Online (Sandbox Code Playgroud)
[非功能性示例:请参阅Jon Skeet的回答]
我最近参加了一个会议,其中乔恩斯基特给通用差异的很好的概述,但我不知道我完全得到它-我理解的重要性in和out关键词,当谈到禁忌和协变,但我我很好奇幕后发生的事情.
执行此代码时CLR会看到什么?是隐式转换List<int>为List<object>或者它是否只是构建在我们现在可以在派生类型之间转换为父类型?
出于兴趣,为什么在以前的版本中没有引入这个,主要的好处是什么 - 即真实世界的使用?
关于Generic Variance的这篇文章的更多信息(但问题非常过时,寻找真实的,最新的信息)
灵感来自现实世界中Scala的共同和逆转的例子,我认为一个更好的问题是:
在设计库时,在确定类型参数是应该是协变还是逆变时,是否应该问自己一组特定的问题?或者你应该让一切都不变,然后根据需要改变?
假设我有一个界面如
public interface IInterface<in TIn, out TOut> {
IInterface<TIn, TOut> DoSomething(TIn input);
}
Run Code Online (Sandbox Code Playgroud)
TIn是禁忌 -variant,并TOut为合作 -variant.
现在,我希望调用者能够指定一些要在输入值上执行的函数,所以天真地我会在接口中添加以下方法:
IInterface<TIn, TOut> DoSomethingWithFunc(Func<TIn, TOut> func);
Run Code Online (Sandbox Code Playgroud)
哪......不起作用.TIn现在需要协变,TOut逆变.
我理解,我不能使用协变泛型类型作为方法的输入,但我想我可以在嵌套泛型类型中使用它们,它本身指定了variance(Func<in T1, out TResult>).
我尝试使用co/contravariant类型创建一个新的委托类型,并更改接口以接受此类型的参数,但无效(相同的错误).
public delegate TOut F<in TDlgIn, out TDlgOut>(TDlgIn input);
public interface IInterface<in TIn, out TOut> {
IInterface<TIn, TOut> DoSomethingWithFunc(F<TIn, TOut> func);
}
Run Code Online (Sandbox Code Playgroud)
我有办法让编译器开心吗?这是否可能(例如使用其他嵌套类型或其他通用参数)?如果没有,为什么不呢?
以下提出了投诉:
interface IInvariant<TInv> {}
interface ICovariant<out TCov> {
IInvariant<TCov> M(); // The covariant type parameter `TCov'
// must be invariantly valid on
// `ICovariant<TCov>.M()'
}
interface IContravariant<in TCon> {
void M(IInvariant<TCon> v); // The contravariant type parameter
// `TCon' must be invariantly valid
// on `IContravariant<TCon>.M()'
}
Run Code Online (Sandbox Code Playgroud)
但我无法想象这不会是类型安全的.(剪断*)这是不允许这样做的原因,还是有其他违反类型安全的情况我不知道?
*我最初的想法确实令人费解,但尽管如此,答案非常彻底,@ Theodoros Chatzigiannakis甚至以令人印象深刻的准确性解剖了我的初步假设.
除了回顾过去的好评之外,我意识到我错误地认为类型签名ICovariant::M仍然是一个Func<IInvariant<Derived>>当它ICovariant<Derived>被分配给a时ICovariant<Base>.然后,该分配M到Func<IInvariant<Base>>会看起来罚款来自未来ICovariant<Base>,但当然会是非法的.为什么不禁止最后这个显然是非法的演员?(所以我认为)
正如埃里克·利珀特(Eric Lippert)所指出的那样,我觉得这种错误和切向猜测会减少这个问题,但出于历史目的,这个被剪切的部分:
对我来说最直观的解释是,以
ICovariant协变为例,TCov …
我是Kotlin的新手.当我在地图中学习存储属性时.我尝试以下用法.
class User(val map: MutableMap<String, String>) {
val name: String by map
}
Run Code Online (Sandbox Code Playgroud)
class User(val map: MutableMap<String, in String>) {
val name: String by map
}
Run Code Online (Sandbox Code Playgroud)
class User(val map: MutableMap<String, out String>) {
val name: String by map
}
Run Code Online (Sandbox Code Playgroud)
前两个都是工作,最后一个都失败了.使用out修饰符,字节码getName如下:
public final java.lang.String getName();
0 aload_0 [this]
1 getfield kotl.User.name$delegate : java.util.Map [11]
4 astore_1
5 aload_0 [this]
6 astore_2
7 getstatic kotl.User.$$delegatedProperties : kotlin.reflect.KProperty[] [15]
10 iconst_0
11 aaload
12 astore_3 …Run Code Online (Sandbox Code Playgroud) 我正在阅读关于泛型方差的一些内容,但我还没有完全理解它,但我想知道它是否可能产生类似下面的内容?
class A<T> { }
class B { }
class C : B { }
class My1 {
public My1(A<B> lessDerivedTemplateParameter)
{
}
}
class My2 : My1 {
public My2(A<C> moreDerivedTemplateParameter)
: base(moreDerivedTemplateParameter) // <-- compile error here, cannot convert
{
}
}
Run Code Online (Sandbox Code Playgroud) 在课程函数式编程课程中,我遇到了一个微妙的概念.
如果A2 <:A1和B1 <:B2,则(A1 => B1)<:( A2 => B2)
理由
如果我们为此画一个维恩图,
参考:Youtube视频
谢谢
generic-variance ×10
c# ×6
covariance ×3
generics ×3
c#-4.0 ×2
scala ×2
.net ×1
.net-4.0 ×1
autofac ×1
class ×1
delegates ×1
jvm ×1
kotlin ×1
resolution ×1
types ×1