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

С# динамический тип

Я просто столкнулся с самой странной штукой, и сейчас я немного mind = blown...

Следующая программа компилируется отлично, но при ее запуске вы получаете RuntimeBinderException при попытке прочитать Value. 'object' does not contain a definition for 'Value'

class Program
{
    interface IContainer
    {
        int Value { get; }
    }

    class Factory
    {
        class Empty : IContainer
        {
            public int Value
            {
                get { return 0; }
            }
        }

        static IContainer nullObj = new Empty();

        public IContainer GetContainer()
        {
            return nullObj;
        }
    }

    static void Main(string[] args)
    {
        dynamic factory = new Factory();
        dynamic container = factory.GetContainer();
        var num0 = container.Value; // WTF!? RuntimeBinderException, really?
    }
}

Здесь часть разума. Переместите вложенный тип Factory+Empty вне класса Factory, например:

class Empty : IContainer
{
    public int Value
    {
        get { return 0; }
    }
}

class Factory...

И программа работает отлично, кто-нибудь может объяснить, почему это?

ИЗМЕНИТЬ

В моем приключении кодирования я, конечно, сделал то, о чем я должен был подумать в первую очередь. Вот почему вы видите, что я немного рассмешался о различии между частным и внутренним. Это было связано с тем, что я установил InternalsVisibleToAttribute, который сделал мой тестовый проект (который потреблял биты в этом экземпляре) вел себя так, как они делали, что было по дизайну, хотя и намекало на меня с самого начала.

Прочитайте ответ Эрика Липперта за хорошее объяснение остальных.

Что меня насторожило, так это то, что динамическое связующее воспринимает видимость типа рассматриваемого экземпляра. У меня много опыта работы с JavaScript, и как программист на JavaScript, где на самом деле нет такой вещи, как публичный или частный, меня полностью обманули тем фактом, что важна видимость, я имею в виду, что я обращался к этому члену так, как будто он был типа публичного интерфейса (я думал, что динамический просто синтаксический сахар для отражения), но динамическое связующее не может сделать такое предположение, если вы не дадите ему намек, используя простой листинг.

4b9b3361

Ответ 1

Фундаментальный принцип "динамического" в С#: во время выполнения выполняет анализ типа выражения, как если бы тип времени выполнения был типом времени компиляции. Поэтому давайте посмотрим, что произойдет, если мы действительно это сделаем:

    dynamic num0 = ((Program.Factory.Empty)container).Value;

Эта программа потерпит неудачу, потому что Empty недоступен. dynamic не позволит вам выполнить анализ, который был бы незаконным в первую очередь.

Однако анализатор времени выполнения реализует это и решает немного обмануть. Он спрашивает себя: "Существует ли базовый класс Пустоты, доступный?" и ответ, очевидно, да. Поэтому он решает вернуться к базовому классу и анализировать:

    dynamic num0 = ((System.Object)container).Value;

Что не удается, потому что эта программа даст вам "объект не имеет члена, называемого значением". Какая ошибка вы получаете.

Динамический анализ никогда не говорит "о, вы, должно быть, имели в виду"

    dynamic num0 = ((Program.IContainer)container).Value;

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

Когда вы перемещаете Empty снаружи, тогда динамический анализатор времени выполнения делает вид, что вы написали:

    dynamic num0 = ((Empty)container).Value;

А теперь Empty доступен, а трансляция законна, поэтому вы получите ожидаемый результат.


UPDATE:

может скомпилировать этот код в сборку, ссылаться на эту сборку, и он будет работать, если пустой тип находится вне класса, который сделает его внутренним по умолчанию.

Я не могу воспроизвести описанное поведение. Попробуем небольшой пример:

public class Factory
{
    public static Thing Create()
    {
        return new InternalThing();
    }
}
public abstract class Thing {}
internal class InternalThing : Thing
{
    public int Value {get; set;}
}

> csc /t:library bar.cs

class P
{
    static void Main ()
    {
        System.Console.WriteLine(((dynamic)(Factory.Create())).Value);
    }
}

> csc foo.cs /r:bar.dll
> foo
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 
'Thing' does not contain a definition for 'Value'

И вы видите, как это работает: связующее средство времени выполнения обнаружило, что InternalThing является внутренним для внешней сборки и поэтому недоступно в foo.exe. Поэтому он возвращается к типу публичной базы, Thing, который доступен, но не имеет необходимого свойства.

Я не могу воспроизвести описанное вами поведение, и если вы можете воспроизвести его, вы обнаружите ошибку. Если у вас есть небольшой репортаж об ошибке, я рад передать ее моим бывшим коллегам.

Ответ 2

Я предполагаю, что во время выполнения вызовы метода контейнера просто разрешаются в закрытом пустом классе, что приводит к сбою вашего кода. Насколько я знаю, динамические не могут использоваться для доступа к закрытым членам (или публичным членам частного класса)

Это должно (конечно) работать:

var num0 = ((IContainer)container).Value;

Здесь это класс Empty, который является приватным: поэтому вы не можете манипулировать пустым экземпляром вне класса объявления (factory). Вот почему ваш код не работает.

Если Empty был внутренним, вы могли бы манипулировать его экземплярами по всей сборке (ну, на самом деле, потому что Factory является закрытым), что позволяет разрешить все динамические вызовы, а ваш код работает.