Прийнята відповідь правильно описує, як слід оголосити список і настійно рекомендується для більшості сценаріїв.
Але я натрапив на інший сценарій, який також охоплює задане питання. Що робити, якщо вам доведеться використовувати наявний список об'єктів, як, наприклад, ViewData["htmlAttributes"]
у MVC ? Як ви можете отримати доступ до його властивостей (вони зазвичай створюються через new { @style="width: 100px", ... }
)?
Для цього трохи іншого сценарію я хочу поділитися з вами тим, що я дізнався. У наведених нижче рішеннях я припускаю наступну декларацію для nodes
:
List<object> nodes = new List<object>();
nodes.Add(
new
{
Checked = false,
depth = 1,
id = "div_1"
});
1. Рішення з динамічним
У версіях C # 4.0 та новіших версіях ви можете просто перейти на динамічну та записати:
if (nodes.Any(n => ((dynamic)n).Checked == false))
Console.WriteLine("found not checked element!");
Примітка. Для цього використовується пізнє прив'язування, а це означає , що він буде розпізнаватись лише під час виконання, якщо об’єкт не має Checked
властивості та передає RuntimeBinderException
в цьому випадку - так що якщо ви спробуєте використати неіснуючу Checked2
властивість, ви отримаєте таке повідомлення на у час виконання: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'"
.
2. Розв’язання з відображенням
Рішення з відображенням працює як зі старими, так і новими версіями компілятора C # . Для старих версій C # врахуйте підказку в кінці цієї відповіді.
Фон
У якості відправної точки, я знайшов хороший відповідь тут . Ідея полягає в перетворенні анонімного типу даних у словник за допомогою відображення. Словник полегшує доступ до властивостей, оскільки їх імена зберігаються як ключі (ви можете отримати доступ до них як myDict["myProperty"]
).
Надихнувшись кодом у посиланні вище, я створив клас розширення, що надає GetProp
, UnanonymizeProperties
і UnanonymizeListItems
як методи розширення, які спрощують доступ до анонімних властивостей. З цим класом ви можете просто виконати запит наступним чином:
if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
Console.WriteLine("found not checked element!");
}
або ви можете використовувати вираз nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()
як if
умову, яке фільтрується неявно, а потім перевіряє, чи є повернуті елементи.
Щоб отримати перший об’єкт, що містить властивість "Перевірено", і повернути його властивість "глибина", ви можете використовувати:
var depth = nodes.UnanonymizeListItems()
?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");
або коротше: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];
Примітка. Якщо у вас є список об'єктів, які не обов'язково містять усі властивості (наприклад, деякі не містять властивості "Перевірено"), і ви все ще хочете створити запит на основі значень "Перевірено", ви можете зробити це:
if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true));
return y.HasValue && y.Value == false;}).Any())
{
Console.WriteLine("found not checked element!");
}
Це запобігає виникненню, KeyNotFoundException
якщо властивості "Перевірено" не існує.
Клас нижче містить такі методи розширення:
UnanonymizeProperties
: Використовується для деанонімності властивостей, що містяться в об'єкті. Цей метод використовує рефлексію. Він перетворює об'єкт у словник, що містить властивості та його значення.
UnanonymizeListItems
: Використовується для перетворення списку об'єктів у список словників, що містять властивості. Він необов'язково може містити лямбда-вираз для попереднього фільтрування .
GetProp
: Використовується для повернення єдиного значення, що відповідає заданому імені властивості. Дозволяє трактувати неіснуючі властивості як нульові значення (true), а не як KeyNotFoundException (false)
Для наведених вище прикладів все, що потрібно - це додати клас розширення нижче:
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;
}
}
}
Підказка: Наведений вище код використовує нуль-умовні оператори, доступні з C # версії 6.0 - якщо ви працюєте з укладачами старше C # (наприклад , C # 3.0), просто замінити ?.
на .
і ?[
на [
скрізь, наприклад ,
var depth = nodes.UnanonymizeListItems()
.FirstOrDefault(n => n.Contains("Checked"))["depth"];
Якщо ви не змушені використовувати старіший компілятор C #, збережіть його так, як є, оскільки використання null-conditionals значно спрощує обробку з нулем.
Примітка. Як і інше рішення з динамічним, це рішення також використовує пізнє прив'язування, але в цьому випадку ви не отримуєте винятку - він просто не знайде елемент, якщо ви посилаєтесь на неіснуючу властивість, як довго як ви зберігаєте нульові умовні оператори.
Що може бути корисним для деяких додатків, це те, що властивість посилається через рядок у рішенні 2, отже, воно може бути параметризовано.