Как написать собственный класс DynamicObject, который поддерживает инициализаторы объектов - PullRequest
3 голосов
/ 07 декабря 2011

В документации для DynamicObject есть пример DynamicDictionary, который позволяет вам работать со словарем, как если бы это был класс со свойствами.

Вот класс (слегка измененный для краткости):

public class DynamicDictionary : DynamicObject
{
    Dictionary<string, object> _dictionary = new Dictionary<string, object>();

    public int Count
    {
        get { return _dictionary.Count; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string name = binder.Name.ToLower();
        return _dictionary.TryGetValue(name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _dictionary[binder.Name.ToLower()] = value;
        return true;
    }
}

Я хотел бы изменить класс, чтобы я мог сделать следующее:

public class Test
{
    public Test()
    {
        var result = Enumerable.Range(1, 5).Select(i => new DynamicDictionary
        {
            Id = i,
            Foo = "Foo",
            Bar = 2
        });
    }
}

Вопросы

  1. Возможно ли это?
  2. Если да, то как?

Ответы [ 4 ]

4 голосов
/ 07 декабря 2011

DynamicObject предоставляет TryCreateInstance(), который предназначен для подобных ситуаций, но его нельзя использовать из C #.

Я вижу несколько способов обойти это:

  1. Создать динамический фабричный класс. Когда вы вызываете метод Create() с именованными аргументами, он передает его в словарь:

    class DynamicDictionaryFactory : DynamicObject
    {
        public override bool TryInvokeMember(
            InvokeMemberBinder binder, object[] args, out object result)
        {
            if (binder.Name == "Create")
            {
                // use binder.CallInfo.ArgumentNames and args
                // to create the dynamic dictionary
                result = …;
                return true;
            }
    
            return base.TryInvokeMember(binder, args, out result);
        }
    }
    
    …
    
    dynamic factory = new DynamicDictionaryFactory();
    
    dynamic dict = factory.Create(Id: 42);
    
  2. Использовать нединамический инициализатор коллекции. Это означает наличие имен свойств в виде строк в коде:

    // has to implement IEnumerable, so that collection initializer works
    class DynamicDictionary
        : DynamicObject, IEnumerable<KeyValuePair<string, object>>
    {
        public void Add(string name, object value)
        {
            m_dictionary.Add(name, value);
        }
    
        // IEnumerable implmentation and actual DynamicDictionary code here
    }
    
    …
    
    dynamic dict = new DynamicDictionary { { "Id", 42 } };
    
  3. Вероятно, наиболее близким к тому, что вы просили, будет использование вложенного инициализатора объекта. То есть класс будет иметь свойство dynamic (скажем, Values), свойства которого можно установить с помощью инициализатора объекта:

    class DynamicDictionary : DynamicObject
    {
        private readonly IDictionary<string, object> m_expandoObject =
            new ExpandoObject();
    
        public dynamic Values
        {
            get { return m_expandoObject; }
        }
    
        // DynamicDictionary implementation that uses m_expandoObject here
    }
    
    …
    
    dynamic dict = new DynamicDictionary { Values = { Id = 42 } };
    
1 голос
/ 08 декабря 2011

Используя открытый исходный текст ImpromptuInterface (через nuget), он имеет Синтаксис Builder .это позволяет вам делать что-то близкое к синтаксису инициализации.В частности, после включения ImpromptuInterface.Dynamic вы можете выполнить

   var result = Enumerable.Range(1, 5).Select(i => Build<DynamicDictionary>.NewObject
    (
        Id: i,
        Foo: "Foo",
        Bar: 2
    ));

. На этой странице синтаксиса также будут перечислены другие опции, если вы уроните <DynamicDictionary>, то будет использоваться ImpromptuDictionary, что по сути то же самое.И вы можете посмотреть на source для синтаксиса сборки.

0 голосов
/ 14 июля 2013

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

public Test()
{
    var data = Enumerable.Range(1, 5).Select(i => new
    {
        Id = i,
        Foo = "Foo",
        Bar = 2
    }.AsDynamicDictionary());
}

Тогда я бы определил метод расширения с кодом для преобразования любых object (включая анонимно набранные) в DynamicDictionary:

public static class DynamicDictionaryExtensions
{
    public static DynamicDictionary AsDynamicDictionary(this object data)
    {
        if (data == null) throw new ArgumentNullException("data");
        return new DynamicDictionary(
               data.GetType().GetProperties()
               .Where(p => p. && p.CanRead)
               .Select(p => new {Name: p.Name, Value: p.GetValue(data, null)})
               .ToDictionary(p => p.Name, p => p.Value)
        );
    }
}

Вы должны реализовать конструктор в DynamicDictionary, чтобы получить IDictionary<string, object>, но это очень просто.

0 голосов
/ 22 декабря 2011

Оказывается, мне удалось решить мою проблему с помощью встроенного в Linq метода ToDictionary().

Пример:

public Test()
{
    var data = Enumerable.Range(1, 5).Select(i => new
    {
        Id = i,
        Foo = "Foo",
        Bar = 2
    });
    var result = data
        .Select(d => d.GetType().GetProperties()
            .Select(p => new { Name = p.Name, Value = p.GetValue(pd, null) })
            .ToDictionary(
                pair => pair.Name,
                pair => pair.Value == null ? string.Empty : pair.Value.ToString()));
}
...