Как получить доступ к свойствам анонимного типа в С#?

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

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

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

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

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

Спасибо

Ответ 1

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

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

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

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

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

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

Ответ 2

Если вы храните объект как тип object, вам нужно использовать отражение. Это относится к любому типу объекта, анонимному или другому. На объекте o вы можете получить его тип:

Type t = o.GetType();

Затем оттуда вы обнаружите свойство:

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

Затем из этого вы можете получить значение:

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

Этот ответ давно запоздал для обновления для С# 4:

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

И теперь другая альтернатива в С# 6:

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

Обратите внимание, что при использовании ?. мы получим результат v null в трех разных ситуациях!

  • o - null, поэтому вообще нет объекта
  • o не null, но не имеет свойства Foo
  • o имеет свойство Foo, но его реальное значение оказывается null.

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

Ответ 3

Вы можете перебирать свойства анонимного типа с помощью 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!");
    }
}

Ответ 4

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

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

В С# 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. Решение с отражением

Решение с отражением работает как со старой, так и с новой версией компилятора С#. Для старых версий С#, пожалуйста, обратите внимание на подсказку в конце этого ответа.

Фон

В качестве отправной точки я нашел хороший ответ здесь. Идея состоит в том, чтобы преобразовать анонимный тип данных в словарь с помощью отражения. Словарь позволяет легко получить доступ к свойствам, так как их имена хранятся в виде ключей (вы можете получить к ним доступ, например, 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.0 версии 6.0 - если вы работаете со старыми компиляторами С# (например, С# 3.0), просто замените ?. по . и ?[ от [ везде, например

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

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

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

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

Ответ 5

Недавно у меня была такая же проблема в .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;

        ...
    }
    ...
}