Как получить доступ к свойству анонимного типа в 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)) ... но это не работает.

Спасибо

wgpubs
источник

Ответы:

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)если оно не существует , будет иметь значение 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!");
    }
}
glennkentwell
источник
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в этом случае выдает a - поэтому, если вы попытаетесь использовать несуществующее 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 значительно упрощает обработку NULL.

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

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

Matt
источник
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;

        ...
    }
    ...
}
orfruit
источник