Как получить доступ к свойству анонимного типа в C #? - PullRequest
100 голосов
/ 30 июля 2009

У меня есть это:

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

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

... и мне интересно, смогу ли я тогда получить свойство "Проверено" анонимного объекта. Я не уверен, возможно ли это вообще. Попробовал сделать это:

if (nodes.Any(n => n["Checked"] == false)) ... но это не работает.

Спасибо

Ответы [ 5 ]

232 голосов
/ 30 июля 2009

Если вы сохраняете объект с типом 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.

Так что это не эквивалентно предыдущим примерам, но может иметь смысл, если вы хотите рассматривать все три случая одинаково.

56 голосов
/ 30 июля 2009

Если вам нужен строго типизированный список анонимных типов, вам нужно также сделать список анонимным типом. Самый простой способ сделать это - проецировать последовательность, такую ​​как массив, в список, например,

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

Тогда вы сможете получить к нему доступ как:

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

Из-за того, как работает компилятор, следующее должно также работать после того, как вы создали список, потому что анонимные типы имеют одинаковую структуру, поэтому они также имеют одинаковый тип. У меня нет компилятора, чтобы проверить это.

nodes.Add(new { Checked = false, /* etc */ });
12 голосов
/ 30 июля 2009

Вы можете перебирать свойства анонимного типа, используя 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!");
    }
}
1 голос
/ 21 ноября 2018

Недавно у меня была такая же проблема в .NET 3.5 (динамическая недоступность). Вот как я решил:

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

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

Адаптировано откуда-то в стеке:

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

        ...
    }
    ...
}
1 голос
/ 12 сентября 2018

Принятый ответ правильно описывает порядок объявления списка и настоятельно рекомендуется для большинства сценариев.

Но я столкнулся с другим сценарием, который также охватывает заданный вопрос. Что если вам нужно использовать существующий список объектов, например 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;
        }
    }
}

Подсказка: Приведенный выше код использует операторы null-conditional , доступные с C # версии 6.0 - если вы работаете с более старыми компиляторами C # (например, C # 3.0 ), просто замените ?. на . и ?[ на [ везде, например

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

Если вы не вынуждены использовать более старый компилятор C #, оставьте его как есть, потому что использование нулевых условий значительно упрощает обработку нуля.

Примечание: Как и другое решение с динамическим, это решение также использует позднюю привязку, но в этом случае вы не получите исключение - оно просто не найдет элемент, если вы ссылаетесь к несуществующему свойству, если вы сохраняете нулевые условные операторы .

Что может быть полезно для некоторых приложений, так это то, что свойство упоминается через строку в решении 2, поэтому его можно параметризировать.

...