如何在C#中访问匿名类型的属性?


125

我有这个:

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

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

...而且我想知道是否可以再获取匿名对象的“ Checked”属性。我不确定这是否可能。尝试这样做:

if (nodes.Any(n => n["Checked"] == false)) ...但是它不起作用。

谢谢

Answers:


63

如果要使用匿名类型的强类型列表,则还需要使该列表成为匿名类型。最简单的方法是将序列(例如数组)投影到列表中,例如

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

然后,您将可以像以下方式访问它:

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

由于编译器的工作方式,创建列表后,以下内容也应工作,因为匿名类型具有相同的结构,因此它们也具有相同的类型。我没有编译器可以验证这一点。

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

263

如果将对象存储为type 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);

注意,通过使用?.我们导致生成的vnull在三种不同的情况!

  1. onull,所以根本没有物体
  2. o不是,null但没有财产Foo
  3. o具有属性,Foo但其实际价值恰好是null

因此,这并不等同于先前的示例,但是如果您想将所有三种情况都视为相同,则可能是有道理的。


4
到目前为止,从未使用过动态。NET4.0的不错更新
Alan

在c#4解决方案中,如果该属性不存在(object v = d.Foo),则将获得运行时异常;如果该属性不存在,GetValue(o, null)则将为null。
YaakovHatam

1
不,GetProperty将返回nullGetValue如果通过则将抛出null,因此总体效果是一个例外。C#4.0版本提供了更具描述性的例外。
Daniel Earwicker 2015年

4
如果要在与源不同的装配中使用动态,则需要使用[InternalsVisibleTo]
Sarath,2017年

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#...
Chris Charabaruk

糟糕,克里斯!有点尴尬...已修复。
glennkentwell,2011年

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"])。

通过在上面的链接代码的启发,我创建的扩展类,它提供GetPropUnanonymizePropertiesUnanonymizeListItems作为扩展方法,它简化了访问匿名属性。使用此类,您可以简单地执行查询,如下所示:

if (nodes.UnanonymizeListItems().Any(n => (bool)n["Checked"] == false))
{
    Console.WriteLine("found not checked element!");
}

或者您可以使用表达式nodes.UnanonymizeListItems(x => (bool)x["Checked"] == false).Any()作为if条件,该条件会进行隐式过滤,然后检查是否返回了任何元素。

要获取第一个包含“ Checked”属性的对象并返回其“ depth”属性,可以使用:

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");

或更短: nodes.UnanonymizeListItems()?.FirstOrDefault(n => n.Contains("Checked"))?["depth"];

注意:如果您有一个对象列表,这些对象不一定包含所有属性(例如,某些对象不包含“ Checked”属性),并且您仍想基于“ Checked”值建立查询,则可以做这个:

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如果“ Checked”属性不存在,这可以防止出现a 。


下面的类包含以下扩展方法:

  • UnanonymizeProperties:用于取消匿名化对象中包含的属性。此方法使用反射。它将对象转换为包含属性及其值的字典。
  • UnanonymizeListItems:用于将对象列表转换为包含属性的字典列表。它可以选择包含一个lambda表达式以进行预先过滤
  • 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处理变得更加容易。

注意:像其他具有动态解决方案的解决方案一样,该解决方案也使用了后期绑定,但是在这种情况下,您不会遇到异常-如果您引用的是不存在的属性,则只要找不到该元素,就不会找到该元素因为您保留了空条件运算符。

对于某些应用程序可能有用的是,该属性是通过解决方案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;

        ...
    }
    ...
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.