为什么调用ISet .Contains()编译,但在运行时抛出exception?
请帮我解释一下这个行为:
dynamic d = 1; ISet s = new HashSet(); s.Contains(d);
代码编译没有错误/警告,但在最后一行我得到以下exception:
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.Generic.ISet' does not contain a definition for 'Contains' at CallSite.Target(Closure , CallSite , ISet`1 , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid2[T0,T1](CallSite site, T0 arg0, T1 arg1) at FormulaToSimulation.Program.Main(String[] args) in
据我所知,这与动态重载分辨率有关,但奇怪的是
(1)如果s的类型是HashSet
,则不会发生exception。
(2)如果我使用非generics接口和接受动态参数的方法,则不会发生exception。
因此,看起来这个问题特别与通用接口有关,但我无法找出导致问题的确切原因。
它是编译器/类型系统中的错误还是合法行为?
到目前为止,您收到的答案并未解释您所看到的行为。 DLR应该找到方法ICollection
并使用盒装整数作为参数调用它,即使变量的静态类型是ISet
而不是ICollection
(因为前者派生)从后者)。
因此,我认为这是一个错误, 我已将其报告给Microsoft Connect。 如果事实certificate这种行为在某种程度上是可取的,那么他们会在那里发表评论。
为什么编译:整个表达式被评估为动态(将鼠标hover在IDE中以确认),这意味着它是运行时检查。
为什么爆炸:我的(完全错误,见下文)猜测是因为你不能以这种方式实现动态接口。 例如,编译器不允许您创建实现ISet
, IEnumerable
, IList
等的类。您会收到一个编译时错误,指出“无法实现动态接口”。 请参阅Chris Burrows关于此主题的博客文章。
http://blogs.msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx
但是,既然它无论如何都要击中DLR,你就可以完全动态了。
dynamic s = new HashSet; s.Contains(d);
编译并运行。
编辑:这个答案的第二部分是完全错误的。 嗯,这是正确的,你不能实现像ISet
这样的接口,但这不是为什么这会爆炸。
请参阅下面的朱利安答案。 您可以获取以下代码进行编译和运行:
ICollection s = new HashSet (); s.Contains(d);
Contains
方法在ICollection
上定义,而不是在ISet
。 CLR不允许从派生接口调用接口基本方法。 您通常不会看到静态解析,因为C#编译器足够聪明,可以发出对ICollection
,而不是不存在的ISet
。
编辑: DLR模仿CLR行为,这就是您获得exception的原因。 您的动态调用是在ISet
,而不是HashSet
,DLR将模仿CLR:对于接口,只搜索接口方法,而不是基本接口(与存在此行为的类相反)。
有关深入解释,请参阅我之前对类似问题的回复:
使用动态类型作为方法参数时的奇怪行为
请注意, dynamic
类型实际上并不存在于运行时。 该类型的变量实际上被编译为object
类型的变量,但编译器将涉及此类对象(作为this
对象或作为参数)的所有方法调用(以及属性和所有内容)转换为动态解析的调用在运行时(使用System.Runtime.CompilerServices.CallSiteBinder
和相关的魔法)。
那么在你的情况下发生的是编译器:
-
将
ISet
转换为ISet
; -
将
HashSet
转换为HashSet
,它将成为您在s
存储的实例的实际运行时类型。
现在,如果你试图调用,比方说,
s.Contains(1);
这实际上没有动态调用就成功了:它实际上只是在盒装整数1
上调用ISet
。
但是如果你试图调用
s.Contains(d);
其中d
是dynamic
,然后编译器将语句转换为在运行时根据d
的运行时类型确定要调用的Contains
的正确重载的语句。 也许现在你可以看到问题:
-
编译器发出的代码肯定会搜索
ISet
类型。 -
该代码确定动态变量在运行时具有
int
类型并尝试查找方法Contains(int)
。 -
ISet
不包含方法Contains(int)
,因此exception。
ISet接口没有’Contains’方法,但是HashSet呢?
编辑我想说的是当给定HashSet concreate类型时绑定器解析’Contains’,但是在界面中找不到inheritance的’Contains’方法…