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 Checked
propiedad y arroja un RuntimeBinderException
en este caso, por lo que si intenta usar una Checked2
propiedad 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
, UnanonymizeProperties
y UnanonymizeListItems
como 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 if
condició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 KeyNotFoundException
produzca 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.