Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting Dynamic Object and Passing Into UnitOfWork and Repository Pattern. Throws Exception

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.

like image 267
thestephenstanton Avatar asked Oct 21 '16 19:10

thestephenstanton


2 Answers

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.

like image 62
Vladimir Reshetnikov Avatar answered Sep 29 '22 06:09

Vladimir Reshetnikov


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.

like image 31
Sunshine Avatar answered Sep 29 '22 05:09

Sunshine