Как я могу использовать синтаксис инициализатора коллекции с ExpandoObject? - PullRequest
15 голосов
/ 06 мая 2011

Я заметил, что новый 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 ]

6 голосов
/ 01 мая 2012

Мне несколько раз требовался простой инициализатор 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"), если вам нужен синтаксис, чтобы быть более кратким.

3 голосов
/ 18 мая 2011

Фреймворк с открытым исходным кодом Dynamitey имеет альтернативный синтаксис для построения ExpandoObject экземпляров inline.

    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;

тоже работает.

3 голосов
/ 06 мая 2011

Насколько я могу судить, языковая спецификация (7.5.10.3 для инициализаторов коллекций) в этом вопросе немного расплывчата. Это говорит

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

К сожалению, в тексте нет подробностей о том, что такое применимый метод Add, но кажется, что явно реализованные методы интерфейса не соответствуют требованиям, так как они по сути считаются частными (см. 13.4.1):

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

...

Явный член интерфейса реализации имеют разные характеристики доступности, чем другие члены. Потому что явный реализации интерфейса никогда не доступны через их полностью квалифицированное имя в вызове метода или доступ к собственности, они находятся в чувство приватности. Тем не менее, так как они могут быть доступным через интерфейс Например, они в некотором смысле также общественности.

2 голосов
/ 07 февраля 2015

Используя идею от @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);
2 голосов
/ 06 мая 2011

Прежде всего, вы на месте.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

ОБНОВЛЕНИЕ

Основная причина - Add был реализованкак protected (без модификатора, который становится protected).

Поскольку Add это не видно на ExpandoObject, он не может быть вызван, как указано выше.

1 голос
/ 30 июля 2015

Жаль, что добавить динамические свойства (имя которых известно только во время выполнения) к 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 для этого, еще один (упомянутый в ответе orad):

dynamic obj = new Expando(new { foo = "hello", bar = 42, baz = new object() });

Будет тривиально реализовать.


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

...