Как я могу использовать синтаксис инициализатора коллекции с ExpandoObject?
Я заметил, что новый ExpandoObject
инвентарь IDictionary<string,object>
который имеет необходимые IEnumerable<KeyValuePair<string, object>>
а также Add(string, object)
методы, и поэтому должна быть возможность использовать синтаксис инициализатора коллекции для добавления свойств к объекту expando так же, как вы добавляете элементы в словарь.
Dictionary<string,object> dict = new Dictionary<string,object>()
{
{ "Hello", "World" }
};
dynamic obj = new ExpandoObject()
{
{ "foo", "hello" },
{ "bar", 42 },
{ "baz", new object() }
};
int value = obj.bar;
Но, кажется, нет способа сделать это. Ошибка:
"System.Dynamic.ExpandoObject" не содержит определения "Добавить"
Я предполагаю, что это не работает, потому что интерфейс реализован явно. но есть ли способ обойти это? Это отлично работает,
IDictionary<string, object> exdict = new ExpandoObject() as IDictionary<string, object>();
exdict.Add("foo", "hello");
exdict.Add("bar", 42);
exdict.Add("baz", new object());
но синтаксис инициализатора коллекции намного аккуратнее.
6 ответов
У меня была потребность в простом инициализаторе ExpandoObject несколько раз, и я обычно использовал следующие два метода расширения для выполнения чего-то вроде синтаксиса инициализатора:
public static KeyValuePair<string, object> WithValue(this string key, object value)
{
return new KeyValuePair<string, object>(key, value);
}
public static ExpandoObject Init(
this ExpandoObject expando, params KeyValuePair<string, object>[] values)
{
foreach(KeyValuePair<string, object> kvp in values)
{
((IDictionary<string, Object>)expando)[kvp.Key] = kvp.Value;
}
return expando;
}
Тогда вы можете написать следующее:
dynamic foo = new ExpandoObject().Init(
"A".WithValue(true),
"B".WithValue("Bar"));
В общем, я обнаружил, что наличие метода расширения для сборки KeyValuePair<string, object>
экземпляры из строкового ключа пригодятся. Очевидно, вы можете изменить имя на что-то вроде Is
так что вы можете написать "Key".Is("Value")
если вам нужен синтаксис, чтобы быть еще более кратким.
Насколько я могу судить, языковая спецификация (7.5.10.3 на Инициализаторах Коллекции) в этом вопросе немного расплывчата. Это говорит
Для каждого указанного элемента по порядку инициализатор коллекции вызывает метод Add для целевого объекта со списком выражений инициализатора элемента в качестве списка аргументов, применяя нормальное разрешение перегрузки для каждого вызова. Таким образом, объект коллекции должен содержать применимый метод Add для каждого инициализатора элемента.
К сожалению, текст не содержит подробностей о том, что такое применимый метод Add, но кажется, что явно реализованные методы интерфейса не соответствуют требованиям, так как они по сути считаются частными (см. 13.4.1):
Невозможно получить доступ к явной реализации элемента интерфейса через его полное имя в вызове метода, доступе к свойству или доступу индексатора. Явная реализация члена интерфейса может быть доступна только через экземпляр интерфейса, и в этом случае на него ссылается просто его имя члена.
...
Явные реализации членов интерфейса имеют другие характеристики доступности, чем другие члены. Поскольку явные реализации членов интерфейса никогда не доступны через их полное имя в вызове метода или в доступе к свойству, в некотором смысле они являются закрытыми. Однако, поскольку к ним можно получить доступ через экземпляр интерфейса, они в некотором смысле также являются публичными.
Фреймворк с открытым исходным кодом Dynamitey имеет альтернативный синтаксис для сборки ExpandoObject
экземпляры встроенные.
dynamic obj = Builder.New<ExpandoObject>(
foo:"hello",
bar: 42 ,
baz: new object()
);
int value = obj.bar;
Он также имеет словарь на основе динамического прототипа объекта Dynamitey.DynamicObjects.Dictionary
такой, что
dynamic obj = new Dynamitey.DynamicObjects.Dictionary()
{
{ "foo", "hello" },
{ "bar", 42 },
{ "baz", new object() }
};
int value = obj.bar;
тоже работает
Обидно, что добавление динамических свойств (имя которых известно только во время выполнения) в ExpandoObject
не так просто, как следовало бы. Все приведение к словарю просто безобразно. Не бери в голову, ты всегда мог написать заказ DynamicObject
который реализует Add
которая помогает вам с аккуратным инициализатором объекта, таким как синтаксис.
Грубый пример:
public sealed class Expando : DynamicObject, IDictionary<string, object>
{
readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return _properties.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (binder.Name == "Add")
{
var del = value as Delegate;
if (del != null && del.Method.ReturnType == typeof(void))
{
var parameters = del.Method.GetParameters();
if (parameters.Count() == 2 && parameters.First().ParameterType == typeof(string))
throw new RuntimeBinderException("Method signature cannot be 'void Add(string, ?)'");
}
}
_properties[binder.Name] = value;
return true;
}
object IDictionary<string, object>.this[string key]
{
get
{
return _properties[key];
}
set
{
_properties[key] = value;
}
}
int ICollection<KeyValuePair<string, object>>.Count
{
get { return _properties.Count; }
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return false; }
}
ICollection<string> IDictionary<string, object>.Keys
{
get { return _properties.Keys; }
}
ICollection<object> IDictionary<string, object>.Values
{
get { return _properties.Values; }
}
public void Add(string key, object value)
{
_properties.Add(key, value);
}
bool IDictionary<string, object>.ContainsKey(string key)
{
return _properties.ContainsKey(key);
}
bool IDictionary<string, object>.Remove(string key)
{
return _properties.Remove(key);
}
bool IDictionary<string, object>.TryGetValue(string key, out object value)
{
return _properties.TryGetValue(key, out value);
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
((ICollection<KeyValuePair<string, object>>)_properties).Add(item);
}
void ICollection<KeyValuePair<string, object>>.Clear()
{
_properties.Clear();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_properties).Contains(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<string, object>>)_properties).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
return ((ICollection<KeyValuePair<string, object>>)_properties).Remove(item);
}
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
{
return _properties.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_properties).GetEnumerator();
}
}
И вы можете позвонить, как вы хотели:
dynamic obj = new Expando()
{
{ "foo", "hello" },
{ "bar", 42 },
{ "baz", new object() }
};
int value = obj.bar;
Предостережение при таком подходе заключается в том, что вы не можете добавить Add
"method" с той же сигнатурой, что и Dictionary.Добавьте к вашему объекту expando, так как он уже является действительным членом Expando
класс (который был необходим для синтаксиса инициализатора коллекции). Код выдает исключение, если вы делаете
obj.Add = 1; // runs
obj.Add = new Action<string, object>(....); // throws, same signature
obj.Add = new Action<string, int>(....); // throws, same signature for expando class
obj.Add = new Action<string, object, object>(....); // runs, different signature
obj.Add = new Func<string, object, int>(....); // runs, different signature
Если имена свойств не должны быть действительно динамичными, то другой альтернативой является ToDynamic
метод расширения, чтобы вы могли инициализировать в строке.
public static dynamic ToDynamic(this object item)
{
var expando = new ExpandoObject() as IDictionary<string, object>;
foreach (var propertyInfo in item.GetType().GetProperties())
expando[propertyInfo.Name] = propertyInfo.GetValue(item, null);
return expando;
}
Так что вы можете позвонить:
var obj = new { foo = "hello", bar = 42, baz = new object() }.ToDynamic();
int value = obj.bar;
Есть сто способов, которыми вы можете разработать API для этого, еще один такой (упомянутый в ответе orad):
dynamic obj = new Expando(new { foo = "hello", bar = 42, baz = new object() });
Будет тривиально для реализации.
Примечание: всегда есть анонимные типы, если вы знаете имена свойств статически и не хотите добавлять дальше после инициализации.
Прежде всего, вы на месте. IDictionary<string,object>
был реализован явно.
Вам даже не нужно кастинг. Это работает:
IDictionary<string,object> exdict = new ExpandoObject()
Теперь синтаксис сбора причины не работает, потому что это реализация в Dictionary<T,T>
конструктор, а не часть интерфейса, следовательно, он не будет работать для эксплореда.
Неверное утверждение выше. Вы правы, он использует функцию добавления:
static void Main(string[] args)
{
Dictionary<string,object> dictionary = new Dictionary<string, object>()
{
{"Ali", "Ostad"}
};
}
Компилируется в
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 27 (0x1b)
.maxstack 3
.locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2<string,object> dictionary,
[1] class [mscorlib]System.Collections.Generic.Dictionary`2<string,object> '<>g__initLocal0')
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,object>::.ctor()
IL_0006: stloc.1
IL_0007: ldloc.1
IL_0008: ldstr "Ali"
IL_000d: ldstr "Ostad"
IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,object>::Add(!0,
!1)
IL_0017: nop
IL_0018: ldloc.1
IL_0019: stloc.0
IL_001a: ret
} // end of method Program::Main
ОБНОВИТЬ
Основная причина Add
был реализован как protected
(без модификатора, который становится protected
).
поскольку Add
не виден на ExpandoObject
, это нельзя назвать как выше.
Используя идею @samedave и добавив отражения, я получил это:
void Main()
{
dynamic foo = new ExpandoObject().Init(new
{
A = true,
B = "Bar"
});
Console.WriteLine(foo.A);
Console.WriteLine(foo.B);
}
public static class ExtensionMethods
{
public static ExpandoObject Init(this ExpandoObject expando, dynamic obj)
{
var expandoDic = (IDictionary<string, object>)expando;
foreach (System.Reflection.PropertyInfo fi in obj.GetType().GetProperties())
{
expandoDic[fi.Name] = fi.GetValue(obj, null);
}
return expando;
}
}
Но было бы лучше иметь возможность сделать это так:
dynamic foo = new ExpandoObject
{
A = true,
B = "Bar"
});
Пожалуйста, проголосуйте за эту функцию в Visual Studio UserVoice.
Обновить:
Используя реализацию Expando Рика Страла, вы сможете сделать что-то вроде этого:
dynamic baz = new Expando(new
{
A = false,
B = "Bar2"
});
Console.WriteLine(baz.A);
Console.WriteLine(baz.B);