为什么调用ISet <dynamic> .Contains()编译,但在运行时抛出异常?</dynamic>

时间:2010-09-12 18:38:53

标签: c# generics dynamic .net-4.0

请帮我解释一下这个行为:

dynamic d = 1;
ISet<dynamic> s = new HashSet<dynamic>();
s.Contains(d);

代码编译时没有错误/警告,但在最后一行我得到以下异常:

Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.Generic.ISet<object>' 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<dynamic>,则不会发生异常。

(2)如果我使用非泛型接口和接受动态参数的方法,则不会发生异常。

因此,看起来这个问题与通用接口有关,但我无法找出导致问题的原因。

它是编译器/类型系统中的错误还是合法行为?

5 个答案:

答案 0 :(得分:9)

到目前为止,您收到的答案并未解释您所看到的行为。 DLR应该找到方法ICollection<object>.Contains(object)并使用盒装整数作为参数调用它,即使变量的静态类型是ISet<dynamic>而不是ICollection<dynamic>(因为前者来源于后者)。

因此,我认为这是一个错误并且 I have reported it to Microsoft Connect. 如果事实证明该行为在某种程度上是可取的,那么他们会在那里发布评论。

答案 1 :(得分:3)

为什么要编译:整个表达式被评估为动态(将鼠标悬停在IDE中以确认),这意味着它是运行时检查。

为什么炸弹:我的(完全错误,见下文)猜测是因为你不能以这种方式实现动态接口。例如,编译器不允许您创建实现ISet<dynamic>IEnumerable<dynamic>IList<dynamic>等的类。您会收到编译时错误,指出“无法实现动态接口” 。请参阅Chris Burrows关于此主题的博客文章。

http://blogs.msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx

然而,既然它正在击中DLR,你可以使s完全动态。

dynamic s = new HashSet<dynamic>;
s.Contains(d);

编译并运行。

编辑:这个答案的第二部分是完全错误的。嗯,这是正确的,你不能实现ISet<dynamic>这样的接口,但这不是为什么这会爆炸。

见朱利安的答案如下。您可以获取以下代码来编译 run:

ICollection<dynamic> s = new HashSet<dynamic>();
s.Contains(d);

答案 2 :(得分:2)

Contains方法在ICollection<T>上定义,而不是ISet<T>。 CLR不允许从派生接口调用接口基本方法。您通常不会看到静态分辨率,因为C#编译器足够聪明,可以发出对ICollection<T>.Contains的调用,而不是不存在的ISet<T>.Contains

编辑: DLR模仿CLR行为,这就是您获得异常的原因。您的动态调用是在ISet<T>上完成的,而不是HashSet<T> DLR将模仿CLR:对于接口,只搜索接口方法,而不是基本接口(与存在此行为的类相反) )。

如需深入解释,请参阅我之前对类似问题的回应:

Strange behaviour when using dynamic types as method parameters

答案 3 :(得分:0)

请注意,dynamic类型在运行时实际上并不存在。该类型的变量实际上被编译为object类型的变量,但编译器会转换涉及此类对象的所有方法调用(以及属性和所有内容)(作为this对象或作为参数)进入在运行时动态解析的调用(使用System.Runtime.CompilerServices.CallSiteBinder和相关的魔法)。

所以在你的情况下会发生的是编译器:

  • ISet<dynamic>变为ISet<object>;

  • HashSet<dynamic>变为HashSet<object>,这将成为您s中存储的实例的实际运行时类型。

现在,如果你试图调用,比方说,

s.Contains(1);

这实际上没有动态调用就成功了:它实际上只是在盒装整数ISet<object>.Contains(object)上调用1

但是如果你试图调用

s.Contains(d);

其中ddynamic,然后编译器将语句转换为在运行时确定基于运行时调用Contains的正确重载的语句 d的类型。也许现在你可以看到问题:

  • 编译器会发出明确搜索ISet<object>类型的代码。

  • 该代码确定动态变量在运行时具有类型int,并尝试查找方法Contains(int)

  • ISet<object>不包含方法Contains(int),因此例外。

答案 4 :(得分:-1)

ISet接口没有'Contains'方法,但是HashSet呢?

修改 我的意思是当给定HashSet concreate类型时,绑定器解析'Contains',但是在界面中找不到继承的'Contains'方法......