#c# #dynamic #compiler-bug
#c# #динамический #ошибка компилятора
Вопрос:
При реализации динамической отправки с использованием dynamic
в универсальном классе, а параметр generic type является частным внутренним классом в другом классе, runtime binder выдает исключение.
Например:
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
будет выдано сообщение
‘Диспетчер.CallDispatch(int)’ недоступен из-за своего уровня защиты
Причина недоступности заключается в том, что параметр type T
является закрытым, к CallType
которому Dispatcher<T>
невозможно получить доступ. Следовательно, CallDispatch
должно быть недоступно — но это не так, потому что оно доступно как T
.
Это ошибка с dynamic
, или это не должно поддерживаться?
Комментарии:
1. Учитывая, что это частичный класс, проблема может заключаться в другом (потому что, очевидно, это не единственный файл).
2. Александр: это единственный файл. Частичное было там, потому что я использовал его раньше как частичный класс и забыл удалить это.
3. Я не думаю, что это ошибка. Я бы предположил, что вещи сделаны закрытыми именно по тем причинам, по которым отображается ваш код — чтобы не вызываться там, где этого не должно быть.
4. Я открыл запрос в Connect: connect.microsoft.com/VisualStudio/feedback/details/672411 /…
Ответ №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()
для того, чтобы сделать статический тип известным, компилятор C # и среда CLR разрешают этому контексту доступ к закрытому типу CallType
, поэтому DLR также должен разрешать это.
Комментарии:
1. @Nathan: Если
Dispatcher<CallType>
у него недостаточно доступа для выполнения вызова, моя версия завершится ошибкой. Но мой код компилируется и выполняется без ошибок. Следовательно, доступно все необходимое, и DLR неправильно выдает ошибку, указывающую иное.2. Натан: Нет, мой вопрос был: «Почему я не могу выполнить этот вызов динамически, когда я могу сделать это статически?» Это ошибка, или я что-то упускаю?»
3. Не каждый статически допустимый вызов действителен динамически. Например:
IList<int> x = new int[10]; Console.WriteLine(x.Count);
работает нормально, но если вы используетеdynamic x = new int[10]; Console.WriteLine(x.Count);
, этого не произойдет, потому чтоCount
это явно реализовано.4. @Ben: Я хочу сказать, что существуют тонкости, выходящие за рамки «если это работает статически, оно должно работать динамически». Не забывайте, что на данный момент он не использует его как универсальный метод (он больше не думает о нем как о T), поэтому он пытается вызвать метод, возвращаемый тип которого недоступен. Я не знаю, классифицировал бы я это как ошибку или просто еще одну динамическую ошибку. Точная природа того, что «должно» работать, довольно нечетко определена.
5. @Ben: Да, ты прав. Это не первый раз, когда я сталкиваюсь с этим в этом примере…
Ответ №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);
}