Как я могу использовать синтаксис инициализатора коллекции с помощью 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());
но синтаксис инициализатора коллекции намного опережает.
Ответы
Ответ 1
У меня возникла необходимость в простейшем инициализаторе 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")
, если вам нужно, чтобы синтаксис был еще более кратким.
Ответ 2
Спецификация языка (7.5.10.3 для инициализаторов коллекции) немного неопределенна по этому вопросу, насколько я могу судить. В нем говорится:
Для каждого заданного элемента в порядке, инициализатор коллекции вызывает Добавить метод на целевом объекте с помощью список выражений элемента инициализатор как список аргументов, применяя нормальное разрешение перегрузки для каждого призывание. Таким образом, сбор объект должен содержать применимый Добавить метод для каждого инициализатора элемента.
К сожалению, в тексте не рассматриваются подробные сведения о применимом методе Add, но кажется, что явно реализованные методы интерфейса не соответствуют законопроекту, поскольку они по сути считаются частными (см. 13.4.1):
Невозможно получить доступ к явный член интерфейса реализации в полном объеме квалифицированное имя в вызове метода, доступа к ресурсам или доступа индексатора. явный член интерфейса реализация может быть доступна только через экземпляр интерфейса и в этом случае, просто имя участника.
...
Явный член интерфейса реализации имеют разные характеристики доступности, чем другие члены. Поскольку явный реализации интерфейса никогда не доступны через их полностью квалифицированное имя в вызове метода или доступа к собственности, они находятся в смысл частный. Однако, поскольку они могут доступ через интерфейс Например, они в некотором смысле общественности.
Ответ 3
В исходной структуре ImpromptuInterface есть альтернативный синтаксис для построение ExpandoObject
экземпляров inline.
dynamic obj = Build<ExpandoObject>.NewObject(
foo:"hello",
bar: 42 ,
baz: new object()
);
int value = obj.bar;
Он также имеет объект динамического прототипа на основе словаря ImpromptuDictionary
, который
dynamic obj = new ImpromptuDictionary()
{
{ "foo", "hello" },
{ "bar", 42 },
{ "baz", new object() }
};
int value = obj.bar;
работает тоже.
Ответ 4
Прежде всего, вы находитесь на месте. IDictionary<string,object>
был реализован явно.
Вам даже не нужно кастинг. Это работает:
IDictionary<string,object> exdict = new ExpandoObject()
Теперь синтаксис сбор причины не работает, потому что это реализация в конструкторе Dictionary<T,T>
, а не в интерфейсе, поэтому он не будет работать для expando.
Неправильное выражение выше. Вы правы, он использует функцию добавления:
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
UPDATE
Основная причина Add
реализована как protected
(без модификатора, который становится protected
).
Так как Add
не отображается на ExpandoObject
, его нельзя вызвать, как указано выше.
Ответ 5
Используя идею из @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.
Update:
Используя Rick Strahl Expando, вы должны сделать что-то вроде этого:
dynamic baz = new Expando(new
{
A = false,
B = "Bar2"
});
Console.WriteLine(baz.A);
Console.WriteLine(baz.B);
Ответ 6
Стыдно, что добавление динамических свойств (имя которых известно только во время выполнения) до 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
"метод" с той же сигнатурой, что и Dictionary.Add, к вашему объекту 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 для этого, другой из них (упомянутый в ответе орада):
dynamic obj = new Expando(new { foo = "hello", bar = 42, baz = new object() });
Будет тривиально реализовать.
Замечание: всегда есть анонимные типы, если вы знаете имена свойств статически, и после инициализации вы не хотите добавлять их дальше.