Различия между ExpandoObject, DynamicObject и динамическим

Ответы:

154

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

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

IDynamicMetaObjectProviderИнтерфейс позволяет классу взять под контроль его поздний связанное поведения.
Когда вы используете dynamicключевое слово для взаимодействия с IDynamicMetaObjectProviderреализацией, DLR вызывает IDynamicMetaObjectProviderметоды, а сам объект решает, что делать.

ExpandoObjectИ DynamicObjectклассы реализации IDynamicMetaObjectProvider.

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

SLaks
источник
2
Что было бы хорошим местом, чтобы узнать больше об этом? Не API, а почему за API? Например, почему ExpandoObject не является производным от DynamicObject, который ищет базовый тип defacto для программирования на основе метода 'Ruby' method_missing '.
Гишу
4
Не могли бы вы добавить несколько примеров использования, где это возможно? Например, как бы я использовал DynamicObject, и каковы преимущества?
ɔɯǝɹ
10
Великолепные ответы без таких примеров, как пирог без сливок сверху.
Теоман Шипахи
68

Я постараюсь дать более четкий ответ на этот вопрос, чтобы четко объяснить, в чем различия между динамическим ExpandoObjectи DynamicObject.

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

ExpandoObjectи DynamicObjectдействительно являются типами. На поверхности они очень похожи друг на друга. Оба класса реализуют IDynamicMetaObjectProvider. Однако, копайте глубже, и вы обнаружите, что они совсем не похожи.

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

  1. DynamicObject не может быть создан напрямую.
  2. Вы ДОЛЖНЫ расширить DynamicObject, чтобы он мог использоваться для вас как разработчика.
  3. Когда вы расширяете DynamicObject, вы теперь можете предоставить ПОЛЬЗОВАТЕЛЬСКИЙ режим поведения в отношении того, как вы хотите, чтобы динамическая диспетчеризация разрешала данные, хранящиеся внутри вашего базового представления данных во время выполнения.
  4. ExpandoObject хранит базовые данные в словаре и т. Д. Если вы реализуете DynamicObject, вы можете хранить данные где угодно и как угодно. (например, как вы получаете и устанавливаете данные при отправке, полностью зависит от вас).

Короче говоря, используйте DynamicObject, когда вы хотите создать свои СОБСТВЕННЫЕ типы, которые можно использовать с DLR, и работать с любыми CUSTOM-режимами, которые вы хотите.

Пример: представьте, что вы хотите иметь динамический тип, который возвращает пользовательское значение по умолчанию при каждой попытке получить элемент, который НЕ существует (то есть не был добавлен во время выполнения). И по умолчанию будет сказано: «Извините, в этой банке нет файлов cookie!». Если вам нужен динамический объект, который ведет себя так, вам нужно контролировать, что происходит, когда поле не найдено. ExpandoObject не позволит вам сделать это. Поэтому вам нужно создать свой собственный тип с уникальным динамическим разрешением (диспетчеризацией) поведения элемента и использовать его вместо готового ExpandoObject.

Вы можете создать тип следующим образом: (Обратите внимание, приведенный ниже код только для иллюстрации и может не работать. Чтобы узнать о том, как правильно использовать DynamicObject, в других местах есть много статей и руководств.)

public class MyNoCookiesInTheJarDynamicObject : DynamicObject
{
    Dictionary<string, object> properties = new Dictionary<string, object>();

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (properties.ContainsKey(binder.Name))
        {
            result = properties[binder.Name];
            return true;
        }
        else
        {
            result = "I'm sorry, there are no cookies in this jar!"; //<-- THIS IS OUR 
            CUSTOM "NO COOKIES IN THE JAR" RESPONSE FROM OUR DYNAMIC TYPE WHEN AN UNKNOWN FIELD IS ACCESSED
            return false;
        }
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        properties[binder.Name] = value;
        return true;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        dynamic method = properties[binder.Name];
        result = method(args[0].ToString(), args[1].ToString());
        return true;
    }
}

Теперь мы можем использовать этот воображаемый класс, который мы только что создали, как динамический тип, который имеет очень нестандартное поведение, если поле не существует.

dynamic d = new MyNoCookiesInTheJarDynamicObject();
var s = d.FieldThatDoesntExist;

//in our contrived example, the below should evaluate to true
Assert.IsTrue(s == "I'm sorry, there are no cookies in this jar!")

ExpandoObjectявляется полной реализацией IDynamicMetaObjectProvider, где команда .NET Framework приняла все эти решения за вас. Это полезно, если вам не нужно никакого пользовательского поведения, и вы чувствуете, что ExpandoObject работает достаточно хорошо для вас (90% времени ExpandoObjectдостаточно). Так, например, смотрите следующее, и что для ExpandoObject, разработчики решили выбросить исключение, если динамический элемент не существует.

dynamic d = new ExpandoObject();

/*
The ExpandoObject designers chose that this operation should result in an 
Exception. They did not have to make that choice, null could 
have been returned, for example; or the designers could've returned a "sorry no cookies in the jar" response like in our custom class. However, if you choose to use 
ExpandoObject, you have chosen to go with their particular implementation 
of DynamicObject behavior.
*/

try {
var s = d.FieldThatDoesntExist;
}
catch(RuntimeBinderException) { ... }

Итак, подведем итог: ExpandoObjectэто просто один из заранее выбранных способов расширения DynamicObject с определенными динамическими режимами диспетчеризации, которые, вероятно, будут работать для вас , но могут не зависеть от ваших конкретных потребностей.

Принимая во внимание, DyanmicObjectчто это вспомогательный BaseType, который делает реализацию ваших собственных типов с уникальным динамическим поведением простой и легкой.

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

Айо я
источник
Очень хорошее объяснение. Только одно техническое исправление: ExpandoObject не наследуется от DynamicObject.
Майк
Небольшое исправление в примере для DynamicObject: при переопределении TryGetMember, если вы вернете false, a RuntimeBinderExceptionбудет брошено при попытке доступа к несуществующему свойству. Чтобы фрагмент действительно работал, вы должны вернуться true.
lluchmk
36

Согласно спецификации языка C # dynamicэто объявление типа. Т.е. dynamic xозначает, что переменная xимеет тип dynamic.

DynamicObjectэто тип, который облегчает реализацию IDynamicMetaObjectProviderи, таким образом, переопределяет специфическое поведение привязки для типа.

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

Брайан Расмуссен
источник
25
dynamicэто не фактический тип ... это просто подсказка, чтобы сказать компилятору использовать позднее связывание для этой переменной. dynamicпеременные фактически объявлены как objectв MSIL
Томас Левеск
1
@Thomas: с точки зрения компилятора это тип, но вы правы, что представление во время выполнения - это Object. Вы найдете выражение «статически типизированный, чтобы быть динамическим» в нескольких презентациях MS.
Брайан Расмуссен
3
@Thomas: и спецификация языка говорит: «C # 4.0 вводит новый статический тип, называемый динамическим».
Брайан Расмуссен
действительно ... Но я думаю, что это сбивает с толку, рассматривая его как тип, так как не существует отношения наследования с такими типами, как DynamicObject или ExpandoObject
Томас Левеск
3
@NathanA Я с тобой здесь. Тем не менее, спецификация языка называет это типом, поэтому я и собираюсь.
Брайан Расмуссен
0

Приведенный выше пример DynamicObjectне дает четкого различия, поскольку он в основном реализует функциональность, которая уже предоставлена ExpandoObject.

В двух ссылках, упомянутых ниже, очень ясно, что с помощью DynamicObjectможно сохранить / изменить фактический тип ( XElementв примере, использованном в ссылках ниже) и лучше контролировать свойства и методы.

https://blogs.msdn.microsoft.com/csharpfaq/2009/09/30/dynamic-in-c-4-0-introducing-the-expandoobject/

https://blogs.msdn.microsoft.com/csharpfaq/2009/10/19/dynamic-in-c-4-0-creating-wrappers-with-dynamicobject/

public class DynamicXMLNode : DynamicObject    
{    
    XElement node;

    public DynamicXMLNode(XElement node)    
    {    
        this.node = node;    
    }

    public DynamicXMLNode()    
    {    
    }

    public DynamicXMLNode(String name)    
    {    
        node = new XElement(name);    
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)    
    {    
        XElement setNode = node.Element(binder.Name);

        if (setNode != null)    
            setNode.SetValue(value);    
        else    
        {    
            if (value.GetType() == typeof(DynamicXMLNode))    
                node.Add(new XElement(binder.Name));    
            else    
                node.Add(new XElement(binder.Name, value));    
        }

        return true;    
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)    
    {    
        XElement getNode = node.Element(binder.Name);

        if (getNode != null)    
        {    
            result = new DynamicXMLNode(getNode);    
            return true;    
        }    
        else    
        {    
            result = null;    
            return false;    
        }    
    }    
}
Дипак Мишра
источник