Is this a bug in dynamic?

流过昼夜 提交于 2019-12-04 10:26:34

问题


When implementing dynamic dispatch using dynamic on a generic class, and the generic type parameter is a private inner class on another class, the runtime binder throws an exception.

For example:

using System;

public abstract class Dispatcher<T> {
    public T Call(object foo) { return CallDispatch((dynamic)foo); }

    protected abstract T CallDispatch(int foo);
    protected abstract T CallDispatch(string foo);
}

public class Program {
    public static void Main() {
        TypeFinder d = new TypeFinder();

        Console.WriteLine(d.Call(0));
        Console.WriteLine(d.Call(""));
    }

    private class TypeFinder : Dispatcher<CallType> {
        protected override CallType CallDispatch(int foo) {
            return CallType.Int;
        }

        protected override CallType CallDispatch(string foo) {
            return CallType.String;
        }
    }

    private enum CallType { Int, String }
}

Here, a RuntimeBinderException will be thrown with the message

'Dispatcher.CallDispatch(int)' is inaccessible due to its protection level

The reason for the inaccessibility is that the type parameter T is the private CallType which Dispatcher<T> cannot access. Therefore, CallDispatch must be inaccessible - but it isn't, because it's accessible as T.

Is this a bug with dynamic, or is this not supposed to be supported?


回答1:


It's a bug. If you can make the call statically (and you can), you should be able to make it dynamically.

Specifically, the following code works:

using System;

public abstract class Dispatcher<T> {
    public T Call(object foo)
    {
        return CallDispatch(((object)(dynamic)foo).ToString());
    }

    protected abstract T CallDispatch(int foo);
    protected abstract T CallDispatch(string foo);
}

public class Program {
    public static void Main() {
        TypeFinder d = new TypeFinder();

        Console.WriteLine(d.Call(0));
        Console.WriteLine(d.Call(""));
    }

    private class TypeFinder : Dispatcher<CallType> {
        protected override CallType CallDispatch(int foo) {
            return CallType.Int;
        }

        protected override CallType CallDispatch(string foo) {
            return CallType.String;
        }
    }

    private enum CallType { Int, String }
}

Note that I've used ToString() to make the static type known, the C# compiler and CLR allow this context to access the private type CallType, so the DLR should allow it as well.




回答2:


It's a bug because the following static typing change should be equivalent

using System;

public abstract class Dispatcher<T>
{
    public T Call(int foo) { return CallDispatch(foo); }
    public T Call(string foo) { return CallDispatch(foo); }

    protected abstract T CallDispatch(int foo);
    protected abstract T CallDispatch(string foo);
}

And it works.

This issue seems to be an issue with the compiler and the dlr calls it makes and the static information the compiler includes in the invocation. It can be worked around with the open source framework ImpromptuInterface that manually setups the dlr calls. With Impromptu by setting the context to this it's getting access permissions from the runtime type which will be TypeFinder.

using System;
using ImpromptuInterface.Dynamic;
public abstract class Dispatcher<T>
{
    protected CacheableInvocation _cachedDynamicInvoke;

    protected Dispatcher()
    {
        _cachedDynamicInvoke= new CacheableInvocation(InvocationKind.InvokeMember, "CallDispatch", argCount: 1, context: this);
    }

    public T Call(object foo)
    {
        return (T) _cachedDynamicInvoke.Invoke(this, foo);
    }

    protected abstract T CallDispatch(int foo);
    protected abstract T CallDispatch(string foo);
}


来源:https://stackoverflow.com/questions/6174972/is-this-a-bug-in-dynamic

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!