Это ошибка в динамике?
При реализации динамической рассылки с использованием dynamic
в общем классе, а параметр generic type является частным внутренним классом в другом классе, связующее средство времени выполнения генерирует исключение.
Например:
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 }
}
Здесь RuntimeBinderException
будет выведено с сообщением
'Dispatcher.CallDispatch(int)' недоступен из-за уровня защиты
Причиной недоступности является то, что параметр типа T
является закрытым CallType
, к которому Dispatcher<T>
не может получить доступ. Поэтому CallDispatch
должен быть недоступен - но это не так, потому что он доступен как T
.
Является ли это ошибкой с dynamic
, или это не должно поддерживаться?
Ответы
Ответ 1
Это ошибка. Если вы можете сделать вызов статически (и вы можете), вы сможете сделать его динамически.
В частности, работает следующий код:
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 }
}
Обратите внимание, что я использовал ToString()
, чтобы сделать статический тип известным, компилятор С# и CLR разрешают этому контексту доступ к закрытому типу CallType
, поэтому DLR также должна его разрешать.
Ответ 2
Это ошибка, потому что следующее статическое изменение набора должно быть эквивалентным
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);
}
И он работает.
Эта проблема, по-видимому, является проблемой с компилятором и вызовами dlr, которые он делает, и статической информацией, которую компилятор включает в вызов. Его можно обойти с открытым исходным кодом ImpromptuInterface, который вручную настраивает вызовы dlr. С помощью Impromptu, установив контекст в this
, он получает разрешения доступа из типа среды выполнения, который будет 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);
}