Ограничения динамического типа в C # - PullRequest
5 голосов
/ 28 августа 2010

Не могли бы вы дать мне несколько причин для ограничения динамического типа в C #? Я читал о них в "Pro C # 2010 и платформе .NET 4". Вот выдержка (если цитирование книг здесь незаконно, сообщите мне, и я удалю выдержку):

Хотя многое может быть определяется с использованием динамического ключевого слова, Есть некоторые ограничения в отношении его использование. Пока они не показывают пробки, знаете что динамические данные элемент не может использовать лямбду выражения или C # анонимные методы при вызове метода. Например, следующий код всегда будет в ошибках, даже если целевой метод действительно принимает параметр делегата который принимает строковое значение и возвращает недействительным.

dynamic a = GetDynamicObject(); 
// Error!  Methods on dynamic data can’t use lambdas! 
a.Method(arg => Console.WriteLine(arg));

Чтобы обойти это ограничение, вы нужно будет работать с основным делегировать напрямую, используя методы, описанные в главе 11 (анонимные методы и лямбда выражения и т. д.). Еще одно ограничение является то, что динамическая точка данных не может понимать любые методы расширения (см. Глава 12). К сожалению, это также включить любое расширение методы, которые приходят из API LINQ. Следовательно, переменная, объявленная с динамическое ключевое слово очень ограничено использовать в LINQ to Objects и других Технологии LINQ:

dynamic a = GetDynamicObject(); 
// Error!  Dynamic data can’t find the Select() extension method! 
var data = from d in a select d;

Заранее спасибо.

Ответы [ 3 ]

16 голосов
/ 28 августа 2010

гипотезы Томаса довольно хороши. Его рассуждения о методах расширения являются точными. В основном, чтобы методы расширения работали, нам нужен сайт вызова, чтобы во время выполнения каким-то образом знать, какие директивы с использованием действовали во время компиляции . У нас просто не было достаточно времени или средств для разработки системы, с помощью которой эта информация могла бы сохраняться на сайте вызовов.

Для лямбда-ситуаций ситуация на самом деле более сложная, чем простая задача определения, собирается ли лямбда-выражение в дерево выражений или делегат. Учтите следующее:

d.M(123)

где d - выражение динамического типа. * Какой объект должен быть передан во время выполнения в качестве аргумента для вызова сайта "М"? Ясно, мы вставляем 123 и передаем это. Затем алгоритм разрешения перегрузки в связывателе времени выполнения просматривает тип времени выполнения d и тип времени компиляции int 123 и работает с этим.

А что если это было

d.M(x=>x.Foo())

Теперь, какой объект мы должны передать в качестве аргумента? У нас нет способа представить «лямбда-метод одной переменной, который вызывает неизвестную функцию с именем Foo для любого типа x».

Предположим, мы хотели реализовать эту функцию: что мы должны реализовать? Во-первых, нам нужен способ для представления несвязанной лямбды . Деревья выражений предназначены только для представления лямбд, где известны все типы и методы . Нам нужно изобрести новый вид «нетипизированного» дерева выражений. И тогда нам нужно будет внедрить все правил лямбда-связывания в связывателе времени выполнения.

Рассмотрим последний пункт. Лямбды могут содержать операторы . Для реализации этой функции требуется, чтобы связыватель времени выполнения содержал весь семантический анализатор для каждого возможного оператора в C # .

Это были порядки величины из нашего бюджета. Мы бы все еще работали над C # 4 сегодня, если бы хотели реализовать эту функцию.

К сожалению, это означает, что LINQ не очень хорошо работает с динамическими, потому что LINQ, конечно, везде использует нетипизированные лямбды. Надеемся, что в какой-то гипотетической будущей версии C # у нас будет более полнофункциональный механизм связывания во время выполнения и возможность делать гомоиконические представления несвязанных лямбд. Но я бы не задержал дыхание в ожидании на твоем месте.

ОБНОВЛЕНИЕ: комментарий требует разъяснения по поводу семантического анализатора.

Рассмотрим следующие перегрузки:

class C {
  public void M(Func<IDisposable, int> f) { ... }
  public void M(Func<int, int> f) { ... }
  ...
}

и звонок

d.M(x=> { using(x) { return 123; } });

Предположим, что d относится к динамическому типу времени компиляции и типу среды выполнения C. Что должен делать компоновщик времени выполнения?

Средство связывания во время выполнения должно определить во время выполнения , может ли выражение x=>{...} преобразовываться в каждый из типов делегатов в каждой из перегрузок M.

Чтобы сделать это, связыватель времени выполнения должен быть в состоянии определить, что вторая перегрузка неприменима. Если бы это было применимо, то вы могли бы использовать int в качестве аргумента для оператора using, но аргумент для оператора using должен быть одноразовым. Это означает, что средство связывания времени выполнения должно знать все правила для оператора using и иметь возможность правильно сообщать, является ли любое возможное использование оператора using допустимым или недопустимым .

Очевидно, что это не ограничивается оператором использования. Связыватель времени выполнения должен знать все правила для всего C # , чтобы определить, можно ли преобразовать лямбда-оператор в данный тип делегата.

У нас не было времени написать механизм выполнения, который по сути был полностью новым компилятором C #, который генерирует деревья DLR, а не IL . Запретив лямбды, нам нужно только написать связыватель времени выполнения, который знает, как связывать вызовы методов, арифметические выражения и несколько других простых видов сайтов вызовов. Разрешение лямбды делает проблему связывания во время выполнения порядка десятков или сотен раз дороже в реализации, тестировании и обслуживании.

9 голосов
/ 28 августа 2010

Лямбда : Я думаю, что одной из причин того, что лямбды не поддерживаются в качестве параметров динамических объектов, является то, что компилятор не знает, компилировать ли лямбда-выражение как делегат или дерево выражений.

Когда вы используете лямбду, компилятор принимает решение в зависимости от типа целевого параметра или переменной.Когда это Func<...> (или другой делегат), он компилирует лямбду как исполняемый делегат.Когда цель равна Expression<...>, она компилирует лямбду в дерево выражений.

Теперь, когда у вас есть тип dynamic, вы не знаете, является ли параметр делегатом или выражением, поэтому компилятор не может решить,что делать!

Методы расширения : Я думаю, что причина в том, что поиск методов расширения во время выполнения будет довольно трудным (и, возможно, также неэффективным).Прежде всего, среда выполнения должна знать, на какие пространства имен ссылаются, используя using.Затем необходимо будет выполнить поиск по всем классам во всех загруженных сборках, отфильтровать те, которые доступны (по пространству имен), а затем выполнить поиск по методам расширения ...

3 голосов
/ 28 августа 2010

Эрик (и Томас) говорят это хорошо, но вот как я об этом думаю.

Это утверждение C #

a.Method(arg => Console.WriteLine(arg)); 

не имеет смысла без лота контекста.Сами лямбда-выражения не имеют типов, а могут быть преобразованы в delegate (или Expression) типы.Таким образом, единственный способ собрать смысл заключается в предоставлении некоторого контекста, который заставляет лямбду преобразовываться в определенный тип делегата.Этот контекст обычно (как в этом примере) разрешает перегрузку;учитывая тип a и доступные перегрузки Method для этого типа (включая элементы расширения), мы, возможно, можем поместить некоторый контекст, который дает значение лямбды.

Без этого контекста, чтобы произвести значение,в конечном итоге вам придется связывать все виды информации о лямбде в надежде каким-то образом связать неизвестные во время выполнения.(Какой IL вы могли бы сгенерировать?)

В противоположность этому, вы поместили туда определенный тип делегата,

a.Method(new Action<int>(arg => Console.WriteLine(arg))); 

Kazam!Все стало просто.Независимо от того, какой код находится внутри лямбда-выражения, мы теперь точно знаем, какой у него тип, а это значит, что мы можем скомпилировать IL так же, как и тело любого метода (теперь мы знаем, например, какой из множества перегрузок Console.WriteLine мыперезвонить).И этот код имеет один конкретный тип (Action<int>), что означает, что связывателю времени выполнения легко увидеть, имеет ли a Method, который принимает этот тип аргумента.

В C #, aголая лямбда почти бессмысленна.В C # лямбдах необходим статический контекст, чтобы придать им смысл и исключить неоднозначности, возникающие из-за многих возможных принуждений и перегрузок.Типичная программа предоставляет этот контекст с легкостью, но в случае dynamic этот важный контекст отсутствует.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...