Каковы истинные преимущества ExpandoObject?

587

Класс ExpandoObject , добавляемый в .NET 4, позволяет произвольно устанавливать свойства объекта во время выполнения.

Есть ли какие-либо преимущества перед использованием Dictionary<string, object>или даже Hashtable ? Насколько я могу судить, это не что иное, как хеш-таблица, к которой вы можете получить доступ с немного более лаконичным синтаксисом.

Например, почему это так:

dynamic obj = new ExpandoObject();
obj.MyInt = 3;
obj.MyString = "Foo";
Console.WriteLine(obj.MyString);

Действительно лучше или существенно отличается, чем:

var obj = new Dictionary<string, object>();
obj["MyInt"] = 3;
obj["MyString"] = "Foo";

Console.WriteLine(obj["MyString"]);

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

Рид Копси
источник

Ответы:

689

Так как я написал статью MSDN, на которую вы ссылаетесь, думаю, я должен ответить на нее.

Во-первых, я ожидал этого вопроса, и поэтому я написал сообщение в блоге, в котором показан более или менее реальный вариант использования ExpandoObject: Dynamic в C # 4.0: введение в ExpandoObject .

Вкратце, ExpandoObject может помочь вам создавать сложные иерархические объекты. Например, представьте, что у вас есть словарь в словаре:

Dictionary<String, object> dict = new Dictionary<string, object>();
Dictionary<String, object> address = new Dictionary<string,object>();
dict["Address"] = address;
address["State"] = "WA";
Console.WriteLine(((Dictionary<string,object>)dict["Address"])["State"]);

Чем глубже иерархия, тем страшнее код. С ExpandoObject он остается элегантным и читабельным.

dynamic expando = new ExpandoObject();
expando.Address = new ExpandoObject();
expando.Address.State = "WA";
Console.WriteLine(expando.Address.State);

Во-вторых, как уже указывалось, ExpandoObject реализует интерфейс INotifyPropertyChanged, который дает вам больший контроль над свойствами, чем словарь.

Наконец, вы можете добавить события в ExpandoObject, как здесь:

class Program
{
   static void Main(string[] args)
   {
       dynamic d = new ExpandoObject();

       // Initialize the event to null (meaning no handlers)
       d.MyEvent = null;

       // Add some handlers
       d.MyEvent += new EventHandler(OnMyEvent);
       d.MyEvent += new EventHandler(OnMyEvent2);

       // Fire the event
       EventHandler e = d.MyEvent;

       e?.Invoke(d, new EventArgs());
   }

   static void OnMyEvent(object sender, EventArgs e)
   {
       Console.WriteLine("OnMyEvent fired by: {0}", sender);
   }

   static void OnMyEvent2(object sender, EventArgs e)
   {
       Console.WriteLine("OnMyEvent2 fired by: {0}", sender);
   }
}

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

Александра Русина
источник
53
Интересно. Спасибо за информацию о событиях. Это было новым для меня.
Рид Копси
16
@AlexandraRusina, откуда он знает, что это событие, когда ты говоришь d.MyEvent = null;, или нет?
Шимми Вайцхандлер
20
Может быть, я что-то упускаю, но это не событие - это простое свойство типа делегата.
Сергей Березовский
7
Первый блок может быть написан с использованием анонимных типов: var expando = new { Address = new { State = "WA" } }; Console.WriteLine(expando.Address.State);я нахожу это более читабельным, но ymmv. И учитывая, что он статически типизирован, он более полезен в этом контексте.
Nawfal
13
@nawfal это не правильно - аноним отличается от Expando. Вы создаете анонимный тип, который затем не может добавлять произвольные свойства.
Доктор Blowhard
75

Одно преимущество - для обязательных сценариев. Сетки данных и сетки свойств будут подбирать динамические свойства через систему TypeDescriptor. Кроме того, привязка данных WPF будет понимать динамические свойства, поэтому элементы управления WPF могут связываться с ExpandoObject более легко, чем словарь.

Совместимость с динамическими языками, которые будут ожидать свойства DLR, а не словарные записи, также может рассматриваться в некоторых сценариях.

itowlson
источник
6
Это кажется , что привязка данных к динамическим объектам нарушается . Пользователь-докладчик eisenbergeffect находится здесь, на SO, и является координатором caliburn.micro. @AlexandraRusina Можете ли вы прокомментировать состояние ошибки и статус «Не исправлю»
surfmuggle
2
Для тех, кому интересно, я в настоящее время могу связываться List<dynamic>и IEnumerable<dynamic>использовать WPF4
Грэм Басс
47

Для меня реальная выгода - это простое связывание данных из XAML:

public dynamic SomeData { get; set; }

...

SomeData.WhatEver = "Yo Man!";

...

 <TextBlock Text="{Binding SomeData.WhatEver}" />
bjull
источник
28

Взаимодействие с другими языками, основанными на DLR1-й причине, о которой я могу думать. Вы не можете передать их, Dictionary<string, object>поскольку это не IDynamicMetaObjectProvider. Еще одним дополнительным преимуществом является то, что он реализует, INotifyPropertyChangedчто означает, что в мире привязки данных WPF он также имеет дополнительные преимущества помимо того, что Dictionary<K,V>может предоставить вам.

Дрю Марш
источник
19

Все дело в удобстве программиста. Я могу представить написание быстрых и грязных программ с этим объектом.

ChaosPandion
источник
9
@J. Хендрикс, не забывай, что он тоже сказал "грязный". У Intellisense есть свои недостатки, однако, это облегчает отладку и поиск ошибок. Лично я все еще предпочитаю статические, а не динамические типы, если я не сталкиваюсь со странным (и всегда редким) случаем.
Фил
+1 для удобства. Однако я считаю, что анонимные типы могут быть одинаково удобны, как простой пакет свойств, и просто лучше по своей статичности.
Nawfal
1
Я бы не хотел использовать его в рабочем коде, но он очень удобен в тестовом коде и может сделать его очень красивым.
Тобиас
14

Я думаю, что это будет иметь синтаксическое преимущество, поскольку вы больше не будете «подделывать» динамически добавленные свойства с помощью словаря.

Это и взаимодействие с динамическими языками, я думаю.

gn22
источник
11

Это пример из отличной статьи MSDN об использовании ExpandoObject для создания динамических специальных типов для входящих структурированных данных (например, XML, Json).

Мы также можем назначить делегата динамическому свойству ExpandoObject :

dynamic person = new ExpandoObject();
person.FirstName = "Dino";
person.LastName = "Esposito";

person.GetFullName = (Func<String>)(() => { 
  return String.Format("{0}, {1}", 
    person.LastName, person.FirstName); 
});

var name = person.GetFullName();
Console.WriteLine(name);

Таким образом, это позволяет нам вводить некоторую логику в динамический объект во время выполнения. Следовательно, вместе с лямбда-выражениями, замыканиями, динамическим ключевым словом и классом DynamicObject мы можем ввести некоторые элементы функционального программирования в наш код C #, которые мы знаем по динамическим языкам как JavaScript или PHP.

sgnsajgon
источник
4

В некоторых случаях это удобно. Я буду использовать его, например, для модульной оболочки. Каждый модуль определяет свой собственный диалог конфигурации, связанный с его настройками. Я предоставляю ему ExpandoObject в качестве Datacontext и сохраняю значения в моей конфигурации Storage. Таким образом, средство записи диалогового окна конфигурации просто привязывается к значению, и оно автоматически создается и сохраняется. (И предоставлены модулю для использования этих настроек, конечно)

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

Это похоже на LINQ, только синтаксический сахар, но иногда это облегчает ситуацию.

Поэтому, чтобы ответить на ваш вопрос напрямую: легче писать и легче читать. Но технически это, по сути, является Dictionary<string,object>(Вы можете даже привести его к одному, чтобы перечислить значения).

n1LWeb
источник
-1
var obj = new Dictionary<string, object>;
...
Console.WriteLine(obj["MyString"]);

Я думаю, что это работает только потому, что у всего есть ToString (), в противном случае вам нужно будет знать, какой это был тип, и привести «объект» к этому типу.


Некоторые из них полезны чаще, чем другие, я пытаюсь быть тщательным.

  1. Может быть гораздо более естественным получить доступ к коллекции, в данном случае к «словарю», используя более прямую точечную запись.

  2. Кажется, что это можно использовать как действительно хороший Tuple. Вы по-прежнему можете называть своих членов "Item1", "Item2" и т. Д. ... но теперь вам не нужно, это также изменчиво, в отличие от Tuple. Это имеет огромный недостаток отсутствия поддержки intellisense.

  3. Вам может быть неудобно с «именами элементов в виде строк», как и со словарем, вы можете чувствовать, что это слишком похоже на «выполнение строк», и это может привести к кодированию соглашений об именах и работе с морфемами и слоги, когда код пытается понять, как использовать члены :-P

  4. Можете ли вы присвоить значение самому ExpandoObject или только его членам? Сравните и сопоставьте с динамическим / динамическим [], используйте тот, который больше соответствует вашим потребностям.

  5. Я не думаю, что dynamic / dynamic [] работает в цикле foreach, вы должны использовать var, но, возможно, вы можете использовать ExpandoObject.

  6. Вы не можете использовать динамический как элемент данных в классе, возможно, потому что это по крайней мере похоже на ключевое слово, возможно, вы можете использовать с ExpandoObject.

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


Будет хорошо, если вы сможете развернуть несколько уровней одновременно.

var e = new ExpandoObject();
e.position.x = 5;
etc...

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

Жаль, что вы не можете заставить код построить некоторые из них и подтолкнуть результаты к intellisense. Я не уверен, как это будет работать, хотя.

Будьте хороши, если они могут иметь ценность, а также членов.

var fifteen = new ExpandoObject();
fifteen = 15;
fifteen.tens = 1;
fifteen.units = 5;
fifteen.ToString() = "fifteen";
etc...
alan2here
источник
-3

После valueTuples, что за класс ExpandoObject? это 6 строк кода с ExpandoObject:

dynamic T = new ExpandoObject();
T.x = 1;
T.y = 2;
T.z = new ExpandoObject();
T.z.a = 3;
T.b= 4;

можно записать в одну строку с кортежами:

var T = (x: 1, y: 2, z: (a: 3, b: 4));

кроме того, с синтаксисом кортежа у вас есть строгий вывод типа и поддержка intlisense

Eng. M.Hamdy
источник
1
Ваши примеры не идентичны в том смысле, что со значением tuple вы не можете написать Tc = 5; после окончания определите T. С ExpandoObject вы можете сделать это, потому что он динамический. Ваш пример со значением tuple очень похож на объявление анонимного типа. Например: var T2 = new {x = 1, y = 2, z = new {a = 3, b = 4}};
LxL
Почему мне нужно написать Tc = 5 без определения? ExpandoObject полезен только при работе с COM-объектами, которые не защищены в .net. В противном случае я никогда не использую этот ExpandoObject, потому что он грязный и содержит ошибки как во время разработки, так и во время выполнения.
англ. М.Хамди
1
Как насчет того, чтобы z сначала был назначен (a: 3, b: 4), а затем позже вы бы хотели, чтобы z имел дополнительное свойство c? Можете ли вы сделать это со значением кортежа?
LxL
Поэтому я хочу сказать, что вы не можете сравнивать ExpandoObject со значением tuple, потому что они предназначены для разных целей. Сравнивая свой путь, вы отклонили функциональность, для которой предназначен ExpandoObject, а именно динамическую структуру.
LxL