¿Cómo acceder a propiedades de tipo anónimo en C #?

125

Tengo esto:

List<object> nodes = new List<object>(); 

nodes.Add(
new {
    Checked     = false,
    depth       = 1,
    id          = "div_" + d.Id
});

... y me pregunto si puedo tomar la propiedad "Comprobada" del objeto anónimo. No estoy seguro de si esto es posible. Intenté hacer esto:

if (nodes.Any(n => n["Checked"] == false)) ... pero no funciona.

Gracias

wgpubs
fuente

Respuestas:

63

Si desea una lista de tipos anónimos fuertemente tipada, también deberá hacer que la lista sea un tipo anónimo. La forma más fácil de hacer esto es proyectar una secuencia como una matriz en una lista, por ejemplo

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();

Entonces podrás acceder a él como:

nodes.Any(n => n.Checked);

Debido a la forma en que funciona el compilador, lo siguiente también debería funcionar una vez que haya creado la lista, ya que los tipos anónimos tienen la misma estructura, por lo que también son del mismo tipo. Sin embargo, no tengo un compilador a mano para verificar esto.

nodes.Add(new { Checked = false, /* etc */ });
Greg Beech
fuente
263

Si está almacenando el objeto como tipo object, debe usar la reflexión. Esto es cierto para cualquier tipo de objeto, anónimo u otro. En un objeto o, puede obtener su tipo:

Type t = o.GetType();

Luego de eso buscas una propiedad:

PropertyInfo p = t.GetProperty("Foo");

Entonces de eso puedes obtener un valor:

object v = p.GetValue(o, null);

Esta respuesta está muy atrasada para una actualización de C # 4:

dynamic d = o;
object v = d.Foo;

Y ahora otra alternativa en C # 6:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);

¡Tenga en cuenta que al usarlo ?.hacemos que el resultado vesté nullen tres situaciones diferentes!

  1. oes decir null, no hay ningún objeto
  2. ono es nullpero no tiene una propiedadFoo
  3. otiene una propiedad Foopero su valor real resulta ser null.

Por lo tanto, esto no es equivalente a los ejemplos anteriores, pero puede tener sentido si desea tratar los tres casos de la misma manera.

Daniel Earwicker
fuente
44
Nunca antes había usado una dinámica hasta ahora, buena actualización para .NET 4.0
Alan
en la solución c # 4 obtendrá una excepción de tiempo de ejecución si la propiedad no existe ( object v = d.Foo), mientras GetValue(o, null)que será nula si no existe.
YaakovHatam
1
No, GetPropertyvolverá nully GetValuelanzará si se pasa eso null, por lo que el efecto general es una excepción. La versión C # 4.0 ofrece una excepción más descriptiva.
Daniel Earwicker
44
Si está utilizando dinámico en un ensamblaje diferente al de la fuente, entonces debe usar [InternalsVisibleTo]
Sarath
2
@DanielEarwicker gracias por completar. También se aplica a los tipos anónimos. Como todas las propiedades generadas para los tipos anónimos son internas.
Sarath
13

Puede iterar sobre las propiedades del tipo anónimo usando Reflection; vea si hay una propiedad "marcada" y si la hay, obtenga su valor.

Vea esta publicación de blog: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Entonces algo como:

foreach(object o in nodes)
{
    Type t = o.GetType();

    PropertyInfo[] pi = t.GetProperties(); 

    foreach (PropertyInfo p in pi)
    {
        if (p.Name=="Checked" && !(bool)p.GetValue(o))
            Console.WriteLine("awesome!");
    }
}
Glennkentwell
fuente
66
Si solo necesita una propiedad y ya conoce su nombre, no tiene sentido revisarlas todas; solo use GetProperty y GetValue. Además, System.out.println es Java, no C # ...
Chris Charabaruk
¡Vaya, así es, Chris! Un poco embarazoso ... arreglado ahora.
Glennkentwell
6

La respuesta aceptada describe correctamente cómo debe declararse la lista y es muy recomendable para la mayoría de los escenarios.

Pero me encontré con un escenario diferente, que también cubre la pregunta formulada. ¿Qué sucede si tiene que usar una lista de objetos existente, como ViewData["htmlAttributes"]en MVC ? ¿Cómo puede acceder a sus propiedades (generalmente se crean a través de new { @style="width: 100px", ... })?

Para este escenario ligeramente diferente, quiero compartir con ustedes lo que descubrí. En las siguientes soluciones, estoy asumiendo la siguiente declaración para nodes:

List<object> nodes = new List<object>();

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});

1. Solución con dinámica

En C # 4.0 y versiones superiores , simplemente puede transmitir a dinámico y escribir:

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found not checked element!");

Nota: Esto está utilizando el enlace tardío, lo que significa que reconocerá solo en tiempo de ejecución si el objeto no tiene una Checkedpropiedad y arroja un RuntimeBinderExceptionen este caso, por lo que si intenta usar una Checked2propiedad no existente , recibirá el siguiente mensaje en tiempo de ejecución: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'" .

2. Solución con reflexión.

La solución con reflexión funciona tanto con las versiones antiguas como con las nuevas del compilador de C # . Para versiones anteriores de C #, tenga en cuenta la sugerencia al final de esta respuesta.

Antecedentes

Como punto de partida, encontré una buena respuesta aquí . La idea es convertir el tipo de datos anónimos en un diccionario utilizando la reflexión. El diccionario facilita el acceso a las propiedades, ya que sus nombres se almacenan como claves (puede acceder a ellas como myDict["myProperty"]).

Inspirado por el código en el enlace anterior, creé una clase de extensión que proporciona GetProp, UnanonymizePropertiesy UnanonymizeListItemscomo métodos de extensión, que simplifican el acceso a propiedades anónimas. Con esta clase, simplemente puede hacer la consulta de la siguiente manera:

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

o puede usar la expresión nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()como ifcondición, que filtra implícitamente y luego verifica si hay algún elemento devuelto.

Para obtener el primer objeto que contiene la propiedad "Comprobada" y devolver su propiedad "profundidad", puede usar:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

o más corto: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

Nota: Si tiene una lista de objetos que no necesariamente contienen todas las propiedades (por ejemplo, algunos no contienen la propiedad "Comprobada") y aún desea crear una consulta basada en los valores "Comprobados", puede hacer esto:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

Esto evita que se KeyNotFoundExceptionproduzca si la propiedad "Comprobada" no existe.


La siguiente clase contiene los siguientes métodos de extensión:

  • UnanonymizeProperties: Se utiliza para anonimizar las propiedades contenidas en un objeto. Este método usa la reflexión. Convierte el objeto en un diccionario que contiene las propiedades y sus valores.
  • UnanonymizeListItems: Se utiliza para convertir una lista de objetos en una lista de diccionarios que contienen las propiedades. Opcionalmente puede contener una expresión lambda para filtrar de antemano.
  • GetProp: Se utiliza para devolver un valor único que coincida con el nombre de propiedad dado. Permite tratar las propiedades no existentes como valores nulos (verdadero) en lugar de como KeyNotFoundException (falso)

Para los ejemplos anteriores, todo lo que se requiere es que agregue la clase de extensión a continuación:

public static class AnonymousTypeExtensions
{
    // makes properties of object accessible 
    public static IDictionary UnanonymizeProperties(this object obj)
    {
        Type type = obj?.GetType();
        var properties = type?.GetProperties()
               ?.Select(n => n.Name)
               ?.ToDictionary(k => k, k => type.GetProperty(k).GetValue(obj, null));
        return properties;
    }

    // converts object list into list of properties that meet the filterCriteria
    public static List<IDictionary> UnanonymizeListItems(this List<object> objectList, 
                    Func<IDictionary<string, object>, bool> filterCriteria=default)
    {
        var accessibleList = new List<IDictionary>();
        foreach (object obj in objectList)
        {
            var props = obj.UnanonymizeProperties();
            if (filterCriteria == default
               || filterCriteria((IDictionary<string, object>)props) == true)
            { accessibleList.Add(props); }
        }
        return accessibleList;
    }

    // returns specific property, i.e. obj.GetProp(propertyName)
    // requires prior usage of AccessListItems and selection of one element, because
    // object needs to be a IDictionary<string, object>
    public static object GetProp(this object obj, string propertyName, 
                                 bool treatNotFoundAsNull = false)
    {
        try 
        {
            return ((System.Collections.Generic.IDictionary<string, object>)obj)
                   ?[propertyName];
        }
        catch (KeyNotFoundException)
        {
            if (treatNotFoundAsNull) return default(object); else throw;
        }
    }
}

Sugerencia: El código anterior está utilizando los operadores condicional nulo , disponibles desde la versión 6.0 de C #: si está trabajando con compiladores de C # más antiguos (por ejemplo, C # 3.0), simplemente reemplácelos ?.por .y ?[en [todas partes, por ejemplo

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

Si estás no ve obligado a utilizar un compilador de C # mayor, mantenerlo lo es, porque el uso de los condicionales nulos hace nula manipulación mucho más fácil.

Nota: Al igual que la otra solución con dinámica, esta solución también está utilizando el enlace tardío, pero en este caso no obtendrá una excepción: simplemente no encontrará el elemento si se refiere a una propiedad no existente, siempre que mientras mantiene los operadores condicional nulo .

Lo que podría ser útil para algunas aplicaciones es que se hace referencia a la propiedad a través de una cadena en la solución 2, por lo tanto, se puede parametrizar.

Mate
fuente
1

Recientemente, tuve el mismo problema dentro de .NET 3.5 (no hay dinámica disponible). Así es como lo resolví:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };

using (frmFind f = new frmFind(args)) 
{
...
...
}

Adaptado de algún lugar en stackoverflow:

// Use a custom cast extension
public static T CastTo<T>(this Object x, T targetType)
{
   return (T)x;
}

Ahora recupera el objeto a través de Cast:

public partial class frmFind: Form
{
    public frmFind(object arguments)
    {

        InitializeComponent();

        var args = arguments.CastTo(new { Title = "", Type = typeof(Nullable) });

        this.Text = args.Title;

        ...
    }
    ...
}
orfruit
fuente