为什么调用 ISet.Contains() 可以编译,但在运行时抛出异常?

发布于 2024-09-18 15:03:10 字数 797 浏览 2 评论 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,则不会出现异常。

(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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

路还长,别太狂 2024-09-25 15:03:10

到目前为止您收到的答案并不能解释您所看到的行为。 DLR 应找到方法 ICollection.Contains(object) 并使用装箱整数作为参数调用它,即使变量的静态类型为 ISet< /code> 而不是 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 is ISet<dynamic> instead of ICollection<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.

围归者 2024-09-25 15:03:10

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

为什么它会失败:我的(完全错误,见下文)猜测是因为你无法以这种方式实现动态接口。例如,编译器不允许您创建实现 ISetIEnumerableIList 的类等等。您会收到编译时错误,指出“无法实现动态接口”。请参阅 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 这样的接口,但这不是它爆炸的原因。

请参阅下面朱利安的回答。您可以获得以下代码来编译并运行:

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

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.

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

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:

ICollection<dynamic> s = new HashSet<dynamic>();
s.Contains(d);
你不是我要的菜∠ 2024-09-25 15:03:10

Contains 方法是在 ICollection 上定义的,而不是在 ISet 上定义的。 CLR 不允许从派生接口调用接口基方法。您通常不会在静态解析中看到这种情况,因为 C# 编译器足够智能,可以发出对 ICollection.Contains 的调用,而不是不存在的 ISet.Contains

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

有关深入的解释,请参阅我之前对类似问题的回复:

使用动态类型作为方法参数时的奇怪行为

The Contains method is defined on ICollection<T>, not ISet<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 to ICollection<T>.Contains, not the non-existing ISet<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 an HashSet<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

-黛色若梦 2024-09-25 15:03:10

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

因此,在您的情况下,编译器会发生以下情况:

  • ISet 转换为 ISet;

  • HashSet 转换为 HashSet,它成为您存储在 s 中的实例的实际运行时类型 code>.

现在,如果您尝试调用,比如说,

s.Contains(1);

这实际上在没有动态调用的情况下成功了:它实际上只是在装箱整数 1 上调用 ISet.Contains(object) 。

但是,如果您尝试调用

s.Contains(d);

where d is dynamic,那么编译器会将语句转换为在运行时确定 Contains 的正确重载的语句> 基于 d运行时类型进行调用。也许现在您可以看到问题了:

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

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

  • ISet不包含方法 Contains(int),因此出现异常。

Note that the type dynamic doesn’t actually exist at run-time. Variables of that type are actually compiled into variables of type object, but the compiler turns all the method calls (and properties and everything) that involve such an object (either as the this object or as a parameter) into a call that is resolved dynamically at runtime (using System.Runtime.CompilerServices.CallSiteBinder and related magic).

So what happens in your case is that the compiler:

  • turns ISet<dynamic> into ISet<object>;

  • turns HashSet<dynamic> into HashSet<object>, which becomes the actual run-time type of the instance you’re storing in s.

Now if you try to invoke, say,

s.Contains(1);

this actually succeeds without a dynamic invocation: it really just calls ISet<object>.Contains(object) on the boxed integer 1.

But if you try to invoke

s.Contains(d);

where d is dynamic, then the compiler turns the statement into one that determines, at runtime, the correct overload of Contains to call based on the runtime type of d. 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 method Contains(int).

  • ISet<object> does not contain a method Contains(int), hence the exception.

余罪 2024-09-25 15:03:10

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...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文