7

This is a very specific problem. Not quite sure how to even word it. Basically I am implementing the unit of work and repository pattern, I have a dynamic object that I convert to an int, but if I use var it will throw an exception when trying to call the method.

I tried to remove all the trivial variables to this problem that I can. For some reason I only see it happen with these two design patterns. The exception I get is Additional information: 'BlackMagic.ITacoRepo' does not contain a definition for 'DoStuff'

Here is the code:

class BlackMagic
{
    static void Main(string[] args)
    {
        dynamic obj = new ExpandoObject();
        obj.I = 69;

        UnitOfWork uow = new UnitOfWork();

        int i1 = Convert.ToInt32(obj.I);
        var i2 = Convert.ToInt32(obj.I);

        if(i1.Equals(i2))
        {
            uow.TacoRepo.DoStuff(i1); // Works fine
            uow.TacoRepo.DoStuff(i2); // Throws Exception
        }
    }
}

class UnitOfWork
{
    public ITacoRepo TacoRepo { get; set; }

    public UnitOfWork()
    {
        TacoRepo = new TacoRepo();
    }
}

class Repo<T> : IRepo<T> where T : class
{
    public void DoStuff(int i)
    {
    }
}

interface IRepo<T> where T : class
{
    void DoStuff(int i);
}

class TacoRepo : Repo<Taco>, ITacoRepo
{
}

interface ITacoRepo : IRepo<Taco>
{
}

class Taco
{
}

EDIT: The main question I am trying to find an answer for, is why would the exception get thrown by calling DoStuff inside the unit of work (while using the repo) but not get thrown if DoStuff existed in the BlackMagic class.

  • Can you toss in a breakpoint before `DoStuff` is called and see what actual runtime type `i2` is? – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 19:50
  • @EdPlunkett it tells me it is an Int32 – thestephenstanton Oct 21 '16 at 19:54
  • Yep, just tried it. The value in `i2` is `System.Int32`. But I think it's behaving as if `i2` is a dynamic reference to something else: If you pass a `dynamic` argument to a method, overload resolution is done at runtime. `uow.TacoRepo.DoStuff((int)i2);` works fine . – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 19:55
  • 2
    This is happening because the runtime uses reflection in order to attempt to determine the type of `i2`. However, since `i2` came from a `ExpandoObject` it's declaring and reflected type are null, and as a result it does not match the `int` requirement of `DoStuff`. Strange indeed, perhaps you should just avoid using dynamic here in that fashion. – Travis J Oct 21 '16 at 19:56
  • @TravisJ I gotcha. So one thing that I'm curious about. In the actual code that this problem comes from, I actually using the Newtonsoft Json library to convert a Json string to a dynamic object like: `dynamic obj = JObject.Parse(strJson)` so is it still the same reason for not working? – thestephenstanton Oct 21 '16 at 20:01
  • To be honest, it is an interesting phenomenon and the reason I only posted that as a comment was in the hopes of someone who could expand on it or at least explain perhaps why the runtime didn't know that Convert.ToInt was an int type when using `var` there. As for your question with regards to parsing a json string into a dynamic object, I think that requires the same type of reflection as a model binder, and with dynamic that option doesn't seem to exist. Unsure though off the top of my head what the connection between this example shown and your issue with parse is wrt dynamic. – Travis J Oct 21 '16 at 20:04
  • @TravisJ What is interesting too that I just noticed, is hovering over i2 in the debugger it says that it is a dynamic object – thestephenstanton Oct 21 '16 at 20:09
  • `var i2 = Int32.Parse(obj.I.ToString())` throws the same exception. `var i2 = Int32.Parse($"{obj.I}");` doesn't. @TravisJ the declaring and reflected types of `i1` are null as well here. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:10
  • [Hmmmm.](http://stackoverflow.com/a/2883781/424129) – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:17
  • @EdPlunkett yeah, so like I said to Travis, in your example, if you hover over your former example, you'll see the debugger will say it is a dynamic object. If you hover over the latter it says it is an int. But at runtime GetType says they are both Int32... which is odd to me – thestephenstanton Oct 21 '16 at 20:18
  • If I understand [Eric Lippert in my link above](http://stackoverflow.com/a/2883781/424129), it's all the compiler messing with you. `GetType()` is runtime. By the compiler's logic, `var i2` means you're happy for `i2` to be dynamic. What's weirding me is that according to my understanding of `dynamic` and overload resolution, it ought to be happy with `DoStuff(int)` -- so clearly my "understanding" isn't. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:22
  • @EdPlunkett I dont have a super high level of understanding of the compiler. But wouldn't when you have `var i2` assigned the `Convert.ToInt32()` method, wouldn't that tell the compiler that `i2` will be an `int`? – thestephenstanton Oct 21 '16 at 20:26
  • I'd have forgiven you for expecting it to take the hint there, yeah. I'm dumbfounded myself. But it wants an explicit cast: `var i2 = (int)Convert.ToInt32(obj.I);` makes i2 non-dynamic. In fact, `var i2 = (int)obj.I;` makes i2 non-dynamic. Maybe more gobsmacked than dumbfounded, if you want to be pedantic. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:28
  • Here's another thing: If I give `UnitOfWork` a method `public void Foo(int x) { }`, I can call `uow.Foo(i2)` with dynamic `i2`. I don't understand how that method differs from `ITacoRepo.DoStuff(int)`. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:32
  • @EdPlunkett When I do this: `var i2 = Convert.ToInt32((object)obj.I);` no more exception. I guess I don't fully understand why the parameters you pass in have any effect on what is returned. I mean `Convert.ToInt32` always returns an `int` so I don't understand why it is dynamic instead – thestephenstanton Oct 21 '16 at 20:33
  • 1
    @EdPlunkett Yeah it is really weird because I tested this problem in many different ways. But it only seemed to happen when I used the unit of work and repository pattern. I was not able to replicate it in any other way. – thestephenstanton Oct 21 '16 at 20:35
  • 1
    While I am not sure of the why, I can at least tell you that the runtime binder is the who in the type determinations. – Travis J Oct 21 '16 at 20:35
  • My impression is the compiler is looking at the parameter to that particular call to `Convert.ToInt32()`, and saying "OK, this is a dynamic parameter. And we're assigning the Int32 value this call returns to something declared `var`, so but a dynamic came in, so we'll go and make that `var` `dynamic` too." It's changing the meaning of that `var` declaration. The return value from `Convert.ToInt32()` isn't changing -- the compiler is changing what happens to it after return. Dump the MSIL and see what you get. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 20:35
  • @EdPlunkett I actually don't have any tools on this machine right now to get it – thestephenstanton Oct 21 '16 at 20:48
  • One thing I do want to note though, if I take DoStuff out of the Repo, and drop it into the BlackMagic class, the problem doesn't happen anymore. But for some reason using these design patterns, it throws the exception. – thestephenstanton Oct 21 '16 at 20:49
  • Possible duplicate of [Why does a method invocation expression have type dynamic even when there is only one possible return type?](http://stackoverflow.com/questions/9382130/why-does-a-method-invocation-expression-have-type-dynamic-even-when-there-is-onl) – Travis J Oct 21 '16 at 21:09
  • So I voted to close this as a duplicate. Let me explain why. The reason that `int` is not being used as the type is because the rule for using implicit conversion on the return type of the method call (in this case ToInt) has several conditions, one of which is violated in this situation, notably that "The primary-expression has compile-time type dynamic." As a result the implicit conversion is not used, and the best accessible type (object) is used as a result. There is no definition for DoStuff(object) and as a result the runtime exception occurs. – Travis J Oct 21 '16 at 21:12
  • @TravisJ But why is i2 an acceptable argument for other methods that take int? (Never mind, just saw Sunshine's answer. But this is not a dupe of that other question -- it's a combination of that one, and the surprising runtime binding behavior) – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 21:16
  • 1
    @TravisJ I agree with Ed on this. You make a good point about the dynamic object conversion, but the error doesn't get thrown if I take `DoStuff` out of the Repo and put it in the main `BlackMagic' class then pass i2 in – thestephenstanton Oct 21 '16 at 21:42
  • @TravisJ I do agree that the link you posted does answer our discussion about the dynamic object conversion. But I am not sure if it completely answers the main question of: Why is the exception thrown. I think that Sunshine's answer is on the right path, but not sure if it completely answers it. – thestephenstanton Oct 21 '16 at 22:02
  • it's weird but can you check what is the type of i2 at runtime if it turn out to be anything other than Int32 then that's the reason - note that the compiler doesn't check dynamic type at compile time – Ibrahim Oct 31 '16 at 04:59

2 Answers2

3

This is one of the bugs I reported to Microsoft more than 5 years ago, soon after the dynamic was introduced. As far as I know, it is considered of a very low priority on their list, and might never be fixed.

Here are simple repro steps:

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        list.CopyTo(array, index); // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.IList' does not contain a definition for 'CopyTo'
    }
}

Here is an explanation of the problem. When a function member (a method or an indexer) is invoked on an expression whose static type is an interface type, and at least one of the arguments to the invocation is of the type dynamic (which means the complete member lookup -- type inference -- overload resolution process is postponed until runtime, and becomes a responsibility of the runtime binder rather than the compiler; only a partial set of checks is performed by the compiler based on incomplete type information), and the member being invoked is inherited by the interface from one of its base interfaces (rather than declared in the interface itself), then the runtime binder fails to properly traverse the tree of the base interfaces to find the inherited member, and throws an exception at runtime, reporting that the required member is not found. Note that it is only the runtime binder's fault -- the compiler properly accepted the invocation (but would reject it, if, for example, you made a typo in the method name).

A possible workaround: cast the expression you invoke a member on to the base interface that actually declares the member you are trying to invoke. For example, the program from the repro steps above could be fixed as follows:

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        ((ICollection) list).CopyTo(array, index); // Works okay
    }
}

Or, if possible, get rid of the dynamic dispatch completely by casting the argument(s) of type dynamic to the type specified in the invoked member's signature.

using System.Collections;

class C
{
    static void Main()
    {
        object[] array = { };
        IList list = new ArrayList();
        list.CopyTo(array, 0); // Works okay
        dynamic index = 0;
        list.CopyTo(array, (int) index); // Works okay
    }
}

Unfortunately, both workarounds might be not helpful if you really want overload resolution to happen at runtime, and among the possible candidates there are both members declared by the interface, and members inherited by it. You would probably need to invent some ad hoc solution in that case, or significantly refactor your program.

Vladimir Reshetnikov
  • 11,750
  • 4
  • 30
  • 51
  • So essentially you're saying, that since the interface `ITacoRepo` doesn't explicitly implement the interface `IRepo`'s `DoStuff` method, it doesn't know where else to look? That it is simply just looking at `ITacoRepo`? So technically a problem in interfaces implementing interfaces when passing dynamics in? – thestephenstanton Nov 01 '16 at 15:23
  • Interfaces cannot implement other interfaces, they can only inherit from them. They can re-declare inherited abstract members, but that does not constitute implementations. Of course, a variable of an interface type at runtime (if not null) will contain a reference to an object of some concrete type that has implementations of all members of the interface, both declared (or re-declared) in it, and inherited. But overload resolution must occur based on the statically known type of the receiver, that is, on the interface type. The runtime binder fails to properly collect inherited candidates. – Vladimir Reshetnikov Nov 01 '16 at 17:03
  • At runtime, though, when you call `GetType` on `i2`, it tells you that it is an `Int32`. Why would it still cause the same problem? I see why it would if you kept it as a `dynamic`, but if the problem happens at runtime, I don't understand why it wouldnt accept an `int` – thestephenstanton Nov 01 '16 at 19:56
  • The variable `i2` is declared as `var` that means its compile-time type is inferred from its initializer. Its initializer contains a sub-expression `obj` of type `dynamic`, so the type of the initializer is also `dynamic`, and so is the type of `i2` (exactly as if you explicitly declared it as `dynamic`). It means that any method invocation using `i2` as an argument has to be resolved dynamically at runtime, using the runtime type of the object it refers to for applicability checks and overload resolution. The bug in the runtime binder prevents it from finding an applicable inherited method. – Vladimir Reshetnikov Nov 01 '16 at 23:07
2

It looks like the RuntimeBinder doesn't traverse the inheritance hierarchy so it only looks in the immediate interface ITacoRepo for a definition of DoStuff.

If you make the the UnitOfWork use IRepo<Taco> instead of ITacoRepo, it is able to find the method definition.

Sunshine
  • 439
  • 2
  • 6
  • 1
    Why would this be true of the `var` i2 and not of the `int` i1? – Travis J Oct 21 '16 at 20:59
  • @TravisJ Because RuntimeBinder isn't involved in the call with i1. That's all compile time. – 15ee8f99-57ff-4f92-890c-b56153 Oct 21 '16 at 21:17
  • 1
    So then why if I created a local `IRepo r = new TacoRepo()` and then call `r.DoStuff(i2)` would I not get the error? – thestephenstanton Oct 21 '16 at 21:46
  • @THEStephenStanton What happens if you define `r` as `ITacoRepo`, like the definition in `UnitOfWork`? I think the problem is the type you're using, not where the variable is. – Kendall Frey Oct 21 '16 at 23:16
  • @KendallFrey So you raise an interesting point that I tried (if I understand you right). If I have just have `TacoRepo` and `ITacoRepo`, and I move the signature `DoStuff` to `ITacoRepo`, then implement it in 'TacoRepo', and just do 'ITacoRepo r = new TacoRepo()' then do 'r.DoStuff(i2)', there is no exception. Which is why it is so weird that it only happens when I use this design pattern – thestephenstanton Oct 21 '16 at 23:49