Как я могу использовать синтаксис инициализатора коллекции с помощью 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() });

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


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