Як отримати доступ до власності анонімного типу в C #?


125

У мене це:

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

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

... і мені цікаво, чи зможу я тоді захопити властивість "Перевірено" анонімного об'єкта. Я не впевнений, чи можливо це навіть можливо. Спробував це зробити:

if (nodes.Any(n => n["Checked"] == false)) ... але це не працює.

Дякую

Відповіді:


63

Якщо ви хочете, щоб сильно набрали список анонімних типів, вам також потрібно буде зробити список анонімним. Найпростіший спосіб зробити це спроектувати послідовності, такі як масив, у список, наприклад

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

Тоді ви зможете отримати доступ до нього так:

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

Через те, як працює компілятор, наступні дії також повинні працювати після створення списку, оскільки анонімні типи мають однакову структуру, тому вони також є одним типом. У мене немає компілятора, який би міг перевірити це.

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

263

Якщо ви зберігаєте об'єкт як тип object, вам потрібно використовувати відображення. Це стосується будь-якого типу об'єкта, анонімного чи іншого. Для об'єкта o ви можете отримати його тип:

Type t = o.GetType();

Потім ви шукаєте властивість:

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

Тоді з цього ви можете отримати значення:

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

Цю відповідь давно назріло для оновлення для C # 4:

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

А тепер ще одна альтернатива в C # 6:

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

Зауважте, що за допомогою ?.ми спричиняємо, що отриманий результат vопинився nullв трьох різних ситуаціях!

  1. oє null, тому об’єкта взагалі немає
  2. oне є, nullале не має властивостіFoo
  3. oмає властивість, Fooале його справжня цінність є null.

Отже, це не рівнозначно попереднім прикладам, але може мати сенс, якщо ви хочете розглянути всі три випадки однаково.


4
Ніколи не використовував динаміки до цього часу, приємне оновлення для .NET 4.0
Алан

у рішенні c # 4 ви отримаєте виняток під час виконання, якщо властивість не існує ( object v = d.Foo), тоді як GetValue(o, null)вона буде нульовою, якщо її немає.
YaakovHatam

1
Ні, GetPropertyповернеться null, і GetValueкине , якщо він прийнятий , що null, таким чином , загальний ефект є винятком. Версія C # 4.0 дає більш описовий виняток.
Даніель Ервікер

4
Якщо ви використовуєте динаміку в іншій збірці, ніж джерело, тоді вам потрібно використовувати [InternalsVisibleTo]
Сарат

2
@DanielEarwicker дякую за завершення. Це стосується також анонімних типів. Оскільки всі властивості, створені для анонімних типів, є внутрішніми.
Сарат

13

Ви можете повторити властивості анонімного типу за допомогою Reflection; подивіться, чи є властивість "Перевірено" і чи є, то отримайте його значення.

Дивіться це повідомлення в блозі: http://blogs.msdn.com/wriju/archive/2007/10/26/c-3-0-anonymous-type-and-net-reflection-hand-in-hand.aspx

Тож щось на кшталт:

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!");
    }
}

6
Якщо вам потрібен лише один ресурс, і ви вже знаєте його ім’я, немає сенсу переглядати їх усі; просто використовуйте GetProperty та GetValue. Також System.out.println - це Java, а не C # ...
Кріс Чарабарук

На жаль, Кріс! Трохи бентежно ... виправлено зараз.
glennkentwell

6

Прийнята відповідь правильно описує, як слід оголосити список і настійно рекомендується для більшості сценаріїв.

Але я натрапив на інший сценарій, який також охоплює задане питання. Що робити, якщо вам доведеться використовувати наявний список об'єктів, як, наприклад, 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, отже, воно може бути параметризовано.


1

Нещодавно у мене була та сама проблема в .NET 3.5 (динаміка недоступна). Ось як я вирішив:

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

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

Адаптований звідкись у stackoverflow:

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

Тепер поверніть об’єкт за допомогою кадра:

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

        InitializeComponent();

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

        this.Text = args.Title;

        ...
    }
    ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.