开发者

Why calling ISet<dynamic>.Contains() compiles, but throws an exception at runtime?

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开发者_如何学C 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?


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.


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);


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


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜