Непредсказуемое поведение в динамике c #

17

Я нашел ошибку (функцию?) во время обучения динамике на C #. Может ли кто-нибудь объяснить мне, почему у меня есть исключение?

static class Program
{
    public static void Main(string[] args)
    {
        dynamic someObj = ConstructSomeObj((Action)(() => Console.WriteLine("wtf")));

        var executer = someObj.Execute;
        executer();         // shows "wtf"
        someObj.Execute();  // throws RuntimeBinderException 

        Console.ReadKey();
    }

    static dynamic ConstructSomeObj(dynamic param) 
        => new { Execute = param };
}

Примечание: typeof как exectuer, так и someObj является динамическим

    
задан Vakun 30.06.2016 в 07:39
источник
  • Какое исключение? И это время компиляции или время выполнения? –  RBarryYoung 30.06.2016 в 07:41
  • Похоже, динамический генерирует код для вызова метода, когда он видит obj.foo () и генерирует код для доступа к полю, когда он видит obj.foo. Это может быть ошибка или, по крайней мере, неожиданное поведение –  csharpfolk 30.06.2016 в 08:02
  • Вероятно, тип информации о методе Execute для someObj не существует во время выполнения, поскольку lambda имеет часть анонимного типа и имеет смысл, что содержимое анонимного типа не является общедоступным. См. Аналогичную проблему для динамических анонимных типов: heartysoft.com/ashic/blog/2010/5/... –  Tetsuya Yamamoto 30.06.2016 в 08:03
  • Пожалуйста, предоставьте данные об исключениях. –  DVK 29.07.2016 в 16:56

2 ответа

11

Давайте посмотрим на следующий код:

using System;
using System.Collections.Generic;


public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("first");

        // works perfectly!!!
        dynamic foo = new { x=(Action)(() => Console.WriteLine("ok")) };
        foo.x();

        // fails
        dynamic foo2 = new { x=(object)(Action)(() => Console.WriteLine("ok2")) };
        foo2.x();

    }
}

dynamic использует отражение для метода и полей объектов доступа, и поскольку он не может знать точные типы, он должен полагаться на информацию о типе, присутствующую в объектах, на которых он работает.

Когда поле x в анонимном типе правильно введено как вызов делегата, foo.x() работает, потому что динамическое может видеть, что значение поля является делегатом.

Когда вы используете

static dynamic ConstructSomeObj(dynamic param) 
    { return new { x = param }; }

, чтобы создать анонимный класс, созданный вами классом, с полем x от типа object (динамический - object за кулисами). Когда вы вызываете obj.x dynamic, этот тип поля является object , и он не мешает проверить, какой именно тип указывает это поле. И поскольку у объекта нет метода Invoke() , такого как делегаты, он генерирует исключение. Если вы измените тип параметра метода на Action , он будет работать.

Я предполагаю, что это решение для проверки типа поля вместо типа значения, которое содержит поле, было принято для обеспечения лучшей производительности. Другими словами, когда вы проверяете тип поля, CallSite класс, сгенерированный dynamic , может быть кэширован и повторно использован позже.

Литература: Ссылка

Ссылка

EDIT: проверено это на моно, может ли кто-нибудь проверить VS

    
ответ дан csharpfolk 30.06.2016 в 08:20
  • Не знаете, какую проверку вы хотели, но работали в окнах, ваша программа выполняет именно то, что вы описали (сначала печатает, а нормально, а затем генерирует исключение). –  Chris 30.06.2016 в 13:30
  • @ Крис, спасибо, к сожалению, .NETFiddle использует устаревший компилятор, и в настоящее время у меня есть доступ только к linux box –  csharpfolk 30.06.2016 в 14:03
2

ОК, интересно. Эти 2 строки будут работать:

Task.Run(someObj.Execute);  
((Action)someObj.Execute)();

Кажется, что компилятор всегда принимает () для динамических типов, и во время выполнения CLR выглядит только «на один уровень глубиной». Таким образом, вы можете помочь здесь, добавив явное приведение или сделайте приведение в действие с помощью Task.Run (). Если это особенность или ошибка !? ... не знаю ;-) ...

    
ответ дан Robert K 30.06.2016 в 08:14
  • .Invoke () также работает, но я думаю, что он скомпилирован с тем же, что и один из ваших примеров –  Martheen 30.06.2016 в 08:18