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

时间:2009-07-29 22:47:13

标签: c# .net object properties anonymous-types

我有这个:

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)) ...但它不起作用。

由于

5 个答案:

答案 0 :(得分:230)

如果您将对象存储为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. onull,因此根本没有任何对象
  2. o不是null,但没有属性Foo
  3. o有一个属性Foo,但其实际值恰好是null
  4. 所以这不等同于前面的例子,但是如果你想对所有三个案例都一样对待它们可能有意义。

答案 1 :(得分:55)

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

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

然后您就可以像访问它一样访问它:

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

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

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

答案 2 :(得分:12)

您可以使用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!");
    }
}

答案 3 :(得分:1)

已接受的答案正确地描述了如何声明列表,并且强烈建议在大多数情况下使用。

但是我遇到了另一种情况,它也涵盖了所提出的问题。 如果必须使用现有的对象列表,例如MVC中的ViewData["htmlAttributes"],该怎么办?您如何访问其属性(通常是通过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!");

注意:这是使用 late绑定,这意味着只有对象没有Checked属性并且在运行时抛出RuntimeBinderException-在这种情况下-因此,如果您尝试使用不存在的Checked2属性,则会在运行时收到以下消息 "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'"

2。反射解决方案

带有反射的解决方案适用于新旧C#编译器版本。对于旧的C#版本,请考虑此答案末尾的提示。

背景

作为起点,我找到了一个很好的答案here。这个想法是通过使用反射将匿名数据类型转换为字典。通过字典,可以轻松访问属性,因为它们的名称存储为键(您可以像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”属性),而您仍想建立一个基于“已检查”值的查询,您可以执行以下操作:

if (nodes.UnanonymizeListItems(x => { var y = ((bool?)x.GetProp("Checked", true)); 
                                      return y.HasValue && y.Value == false;}).Any())
{
    Console.WriteLine("found not checked element!");
}

如果“ Checked”属性不存在,这可以防止发生KeyNotFoundException


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

  • 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;
        }
    }
}

提示:上面的代码使用的是null-conditional运算符,自C#6.0版开始可用-如果您使用的是较旧的C#编译器(例如C#3.0),< / strong>只需在各处(例如

)将?.替换为.,将?[替换为[
var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];

如果您不是被迫使用较旧的C#编译器,则应按原样保留它,因为使用空值条件会使空值处理更加容易。

注意:与其他具有动态解决方案的解决方案一样,该解决方案也使用了后期绑定,但是在这种情况下,您不会遇到异常-如果您遇到这种情况,它将根本找不到元素只要保留null-conditional运算符,就可以指代不存在的属性。

对于某些应用程序可能有用的是,该属性是通过解决方案2中的字符串引用的,因此可以对其进行参数化。

答案 4 :(得分: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;

        ...
    }
    ...
}