Pregunta

En la documentación para Objeto dinámico, hay un ejemplo de DynamicDictionary que te permite trabajar con un diccionario como si fuera una clase con propiedades.

Aquí está la clase (modificada ligeramente por brevedad):

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;
    }
}

Lo que me gustaría hacer es modificar la clase para poder hacer lo siguiente:

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

Preguntas

  1. es posible?
  2. Si es así, ¿cómo?
¿Fue útil?

Solución 3

Resulta que pude resolver mi problema utilizando la función integrada de Linq. ToDictionary() método.

Ejemplo:

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()));
}

Otros consejos

DynamicObject proporciona TryCreateInstance(), que está pensado para situaciones como esta, pero no se puede utilizar desde C#.

Veo algunas formas de evitar esto:

  1. Cree una clase de fábrica dinámica.Cuando lo llamas Create() método con argumentos con nombre, lo pasa al diccionario:

    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. Utilice un inicializador de colección no dinámico.Esto significa tener los nombres de las propiedades como cadenas en el código:

    // 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. Probablemente lo más cercano a lo que pediste sería usar anidado inicializador de objetos.Es decir, la clase tendrá un dynamic propiedad (digamos, Values), cuyas propiedades se pueden configurar usando el inicializador de objetos:

    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 } };
    

Usando la ImpromptuInterface de código abierto (a través de nuget), tiene una Sintaxis del constructor.eso te permite hacer algo parecido a la sintaxis de inicialización.Específicamente después de incluir ImpromptuInterface.Dynamic Podrías hacerlo

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

También hay otras opciones enumeradas en esa página de sintaxis si suelta el <DynamicDictionary> utilizará un ImpromptuDictionary que es esencialmente lo mismo.Y puedes mirar el fuente para la sintaxis de compilación también.

Cuando veo un patrón de lenguaje integrado como este, tiendo a usar métodos de extensión, por lo que podría escribir el siguiente código para lograr mi objetivo:

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

Luego definiría el método de extensión con el código para convertir cualquier object (incluidos los escritos de forma anónima) para 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)
        );
    }
}

Tendrías que implementar un constructor en DynamicDictionary para recibir un IDictionary<string, object>, pero eso es pan comido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top