Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 976
Andrey Breslav Avatar asked Sep 12 '10 18:09

Andrey Breslav


3 Answers

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.

like image 103
Timwi Avatar answered Nov 19 '22 10:11

Timwi


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);
like image 25
Anthony Pegram Avatar answered Nov 19 '22 12:11

Anthony Pegram


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

like image 2
Julien Lebosquain Avatar answered Nov 19 '22 11:11

Julien Lebosquain