Как использовать синтаксис инициализатора коллекции с ExpandoObject?

15

Я заметил, что новый 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' does not contain a definition for 'Add'

Я предполагаю, что это не работает, потому что интерфейс реализован явно. но есть ли способ обойти это? Это отлично работает,

IDictionary<string, object> exdict = new ExpandoObject() as IDictionary<string, object>();
exdict.Add("foo", "hello");
exdict.Add("bar", 42);
exdict.Add("baz", new object());

но синтаксис инициализатора коллекции намного лучше.

    
задан eddwo 06.05.2011 в 12:54
источник
  • Пожалуйста, проголосуйте за эту функцию в Visual Studio UserVoice. –  orad 06.02.2015 в 21:31
  • Связано: stackoverflow.com/questions/4216309/... –  nawfal 28.07.2015 в 08:51

6 ответов

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

    
ответ дан daveaglick 01.05.2012 в 20:31
  • Я получил немного более простой синтаксис, используя отражение. Увидеть. –  orad 06.02.2015 в 23:50
3

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

For each specified element in order, the collection initializer invokes an Add method on the target object with the expression list of the element initializer as argument list, applying normal overload resolution for each invocation. Thus, the collection object must contain an applicable Add method for each element initializer.

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

It is not possible to access an explicit interface member implementation through its fully qualified name in a method invocation, property access, or indexer access. An explicit interface member implementation can only be accessed through an interface instance, and is in that case referenced simply by its member name.

...

Explicit interface member implementations have different accessibility characteristics than other members. Because explicit interface member implementations are never accessible through their fully qualified name in a method invocation or a property access, they are in a sense private. However, since they can be accessed through an interface instance, they are in a sense also public.

    
ответ дан Brian Rasmussen 06.05.2011 в 13:35
  • Это одна из вещей, которые я действительно хотел бы исправить в следующей версии C #! –  Gabe 17.05.2011 в 23:11
3

Платформа с открытым исходным кодом 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;

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

    
ответ дан jbtule 17.05.2011 в 23:04
  • Это очень удобно. –  Jeff Mercado 18.05.2011 в 00:05
2

Прежде всего, вы на месте. 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 , его нельзя вызвать, как указано выше.

    
ответ дан Aliostad 06.05.2011 в 13:11
  • Я считаю, что вы ошибаетесь. Если я создаю класс, который реализует, например, IDictionary <строка, объект>, как в примере, мне разрешено использовать инициализатор коллекции, такой как var x = new MyDict () {{foo "," bar "}}; –  Brian Rasmussen 06.05.2011 в 13:18
  • Что вы подразумеваете под его реализацией в конструкторе? –  BoltClock♦ 06.05.2011 в 13:21
  • ОК, я с ума сошел. –  Aliostad 06.05.2011 в 13:37
2

Используя идею из @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"
});

Please vote for this feature in Visual Studio UserVoice.

Update:

Используя реализацию Expando Рика Страла, вы сможете сделать что-то вроде этого:

dynamic baz = new Expando(new
{
    A = false,
    B = "Bar2"
});

Console.WriteLine(baz.A);
Console.WriteLine(baz.B);
    
ответ дан orad 06.02.2015 в 23:21
1

Жаль, что добавить динамические свойства (имя которых известно только во время выполнения) в 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.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() });

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

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

    
ответ дан nawfal 30.07.2015 в 16:53