为什么调用 ISet.Contains() 可以编译,但在运行时抛出异常?
请帮助我解释以下行为:
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
,则不会出现异常。
(2) 如果我使用带有接受动态参数的方法的非泛型接口,则不会发生异常。
因此,看起来这个问题与通用接口特别相关,但我无法找出到底是什么导致了问题。
这是编译器/类型系统中的错误,还是合法行为?
Please, help me to explain the following behavior:
dynamic d = 1;
ISet<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
The code compiles with no errors/warnings, but at the last line I get the following exception:
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
As far as I can tell, this is related to dynamic overload resolution, but the strange things are
(1) If the type of s is HashSet<dynamic>
, no exception occurs.
(2) If I use a non-generic interface with a method accepting a dynamic argument, no exception occurs.
Thus, it looks like this problem is related particularly with generic interfaces, but I could not find out what exactly causes the problem.
Is it a bug in the compiler/typesystem, or legitimate behavior?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
到目前为止您收到的答案并不能解释您所看到的行为。 DLR 应找到方法
ICollection
因此,我相信这是一个错误,我已将其报告给 Microsoft Connect。 如果事实证明该行为是不知怎的,他们会在那里发表评论。
The answers you have received so far do not explain the behaviour you are seeing. The DLR should find the method
ICollection<object>.Contains(object)
and call it with the boxed integer as a parameter, even if the static type of the variable isISet<dynamic>
instead ofICollection<dynamic>
(because the former derives from the latter).Therefore, I believe this is a bug and I have reported it to Microsoft Connect. If it turns out that the behaviour is somehow desirable, they will post a comment to that effect there.
为什么编译:整个表达式被评估为动态(将鼠标悬停在 IDE 内的它上方进行确认),这意味着它是运行时检查。
为什么它会失败:我的(完全错误,见下文)猜测是因为你无法以这种方式实现动态接口。例如,编译器不允许您创建实现
ISet
、IEnumerable
、IList
的类等等。您会收到编译时错误,指出“无法实现动态接口”。请参阅 Chris Burrows 关于此主题的博客文章。http://blogs. msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx
但是,由于它无论如何都会影响 DLR,因此您可以完全创建
s
动态的。编译并运行。
编辑:这个答案的第二部分是完全错误的。好吧,这是正确的,因为您无法实现像
ISet
这样的接口,但这不是它爆炸的原因。请参阅下面朱利安的回答。您可以获得以下代码来编译并运行:
Why it compiles: the entire expression is evaluated as dynamic (hover your mouse over it inside your IDE to confirm), which means that it is a runtime check.
Why it bombs: My (completely wrong, see below) guess is that it is because you cannot implement a dynamic interface in such a manner. For example, the compiler does not allow you to create a class that implements
ISet<dynamic>
,IEnumerable<dynamic>
,IList<dynamic>
, etc. You get a compile-time error stating "cannot implement a dynamic interface". See Chris Burrows' blog post on this subject.http://blogs.msdn.com/b/cburrows/archive/2009/02/04/c-dynamic-part-vii.aspx
However, since it's hitting the DLR anyway, you can make
s
completely dynamic.Compiles and runs.
Edit: the second part of this answer is completely wrong. Well, it is correct in that you can't implement such an interface as
ISet<dynamic>
, but that's not why this blows up.See Julian's answer below. You can get the following code to compile and run:
Contains
方法是在ICollection
上定义的,而不是在ISet
上定义的。 CLR 不允许从派生接口调用接口基方法。您通常不会在静态解析中看到这种情况,因为 C# 编译器足够智能,可以发出对ICollection.Contains
的调用,而不是不存在的ISet.Contains
。编辑: DLR 模仿 CLR 行为,这就是您收到异常的原因。您的动态调用是在
ISet
上完成的,而不是在HashSet
上完成的,DLR 将模仿 CLR:对于接口,仅搜索接口方法,不是基接口(与存在此行为的类相反)。有关深入的解释,请参阅我之前对类似问题的回复:
使用动态类型作为方法参数时的奇怪行为
The
Contains
method is defined onICollection<T>
, notISet<T>
. The CLR doesn't allow an interface base method to be called from a derived interface. You usually doesn't see this with static resolution because the C# compiler is smart enough to emit a call toICollection<T>.Contains
, not the non-existingISet<T>.Contains
.Edit: The DLR mimics the CLR behavior, that's why you get the exception. Your dynamic call is done on an
ISet<T>
, not anHashSet<T>
the DLR will mimics the CLR: for an interface, only interfaces methods are searched for, not base interfaces (contrary to classes where this behavior is present).For an in-depth explanation, see a previous response of mine to a similar question:
Strange behaviour when using dynamic types as method parameters
请注意,
dynamic
类型在运行时实际上并不存在。该类型的变量实际上被编译为object
类型的变量,但是编译器将涉及此类对象的所有方法调用(以及属性和所有内容)(或者作为this
> 对象或作为参数)到在运行时动态解析的调用(使用 System.Runtime.CompilerServices.CallSiteBinder 和相关魔法)。因此,在您的情况下,编译器会发生以下情况:
将
ISet
转换为ISet
将
HashSet
转换为HashSet
现在,如果您尝试调用,比如说,
这实际上在没有动态调用的情况下成功了:它实际上只是在装箱整数
1
上调用ISet
但是,如果您尝试调用
where
d
isdynamic
,那么编译器会将语句转换为在运行时确定Contains
的正确重载的语句> 基于d
的运行时类型进行调用。也许现在您可以看到问题了:编译器发出明确搜索类型
ISet
该代码确定动态变量在运行时的类型为
int
,并尝试查找方法Contains(int)
。ISet
Note that the type
dynamic
doesn’t actually exist at run-time. Variables of that type are actually compiled into variables of typeobject
, but the compiler turns all the method calls (and properties and everything) that involve such an object (either as thethis
object or as a parameter) into a call that is resolved dynamically at runtime (usingSystem.Runtime.CompilerServices.CallSiteBinder
and related magic).So what happens in your case is that the compiler:
turns
ISet<dynamic>
intoISet<object>
;turns
HashSet<dynamic>
intoHashSet<object>
, which becomes the actual run-time type of the instance you’re storing ins
.Now if you try to invoke, say,
this actually succeeds without a dynamic invocation: it really just calls
ISet<object>.Contains(object)
on the boxed integer1
.But if you try to invoke
where
d
isdynamic
, then the compiler turns the statement into one that determines, at runtime, the correct overload ofContains
to call based on the runtime type ofd
. Perhaps now you can see the problem:The compiler emits code that definitely searches the type
ISet<object>
.That code determines that the dynamic variable has type
int
at runtime and tries to find a methodContains(int)
.ISet<object>
does not contain a methodContains(int)
, hence the exception.ISet 接口没有“Contains”方法,但是 HashSet 有吗?
编辑
我的意思是,当给定 HashSet concreate 类型时,绑定器解析“Contains”,但在接口中找不到继承的“Contains”方法...
ISet interface does not have a method 'Contains', HashSet does however?
EDIT
What i meant to say was the binder resolves 'Contains' when given the HashSet concreate type, but doesnt find the inherited 'Contains' method in the interface...