Подтвердить что ты не робот

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

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

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 является динамическим

4b9b3361

Ответ 1

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

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, может быть кэширован и повторно использован позже.

Литература: https://github.com/mono/mono/blob/ef407901f8fdd9ed8c377dbec8123b5afb932ebb/mcs/class/Microsoft.CSharp/Microsoft.CSharp.RuntimeBinder/Binder.cs

https://github.com/mono/mono/blob/ef407901f8fdd9ed8c377dbec8123b5afb932ebb/mcs/class/Microsoft.CSharp/Microsoft.CSharp.RuntimeBinder/CSharpInvokeMemberBinder.cs

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

Ответ 2

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

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

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