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

wgp*_*ubs 110 .net c# properties object anonymous-types

我有这个:

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

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

...而且我想知道我是否可以获取匿名对象的"已检查"属性.我不确定这是否可行.试过这样做:

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

谢谢

Dan*_*ker 244

如果要将对象存储为类型object,则需要使用反射.对于任何对象类型(匿名或其他)都是如此.在对象o上,您可以获得其类型:

Type t = o.GetType();
Run Code Online (Sandbox Code Playgroud)

然后从那里你查找一个属性:

PropertyInfo p = t.GetProperty("Foo");
Run Code Online (Sandbox Code Playgroud)

然后从那里你可以得到一个值:

object v = p.GetValue(o, null);
Run Code Online (Sandbox Code Playgroud)

对于C#4的更新,这个答案早就应该是:

dynamic d = o;
object v = d.Foo;
Run Code Online (Sandbox Code Playgroud)

现在是C#6的另一个替代方案:

object v = o?.GetType().GetProperty("Foo")?.GetValue(o, null);
Run Code Online (Sandbox Code Playgroud)

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

  1. o是的null,所以没有任何对象
  2. o是非,null但没有财产Foo
  3. o有一个属性,Foo但它的真正价值恰好是null.

所以这不等同于前面的例子,但如果你想对所有三种情况都一视同仁,这可能是有意义的.

  • 从来没有使用动态到现在为止,.NET 4.0的更新 (4认同)
  • 如果你在不同的程序集中使用动态而不是源,那么你需要使用[InternalsVisibleTo] (4认同)
  • @DanielEarwicker感谢您的完成.它也适用于匿名类型.因为为匿名类型生成的所有属性都是内部的. (2认同)
  • 如果您遇到“RuntimeBinder.RuntimeBinderException”,指出“object”不包含定义“some-property”,则需要在“AssemblyInfo.cs”中添加“InternalsVisibleTo”属性,请参阅 https:// juristr.com/blog/2013/08/object-does-not-contain-definition/ 了解更多信息。 (2认同)

Gre*_*ech 59

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

var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();
Run Code Online (Sandbox Code Playgroud)

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

nodes.Any(n => n.Checked);
Run Code Online (Sandbox Code Playgroud)

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

nodes.Add(new { Checked = false, /* etc */ });
Run Code Online (Sandbox Code Playgroud)


gle*_*ell 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!");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你只需要一个属性并且你已经知道它的名字,那么通过所有这些属性是没有意义的; 只需使用GetProperty和GetValue.另外,System.out.println是Java,而不是C#... (6认同)

Mat*_*att 9

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

但是我遇到了一个不同的场景,它也涵盖了所提出的问题。如果您必须使用现有的对象列表,例如ViewData["htmlAttributes"]MVC 中怎么办?你如何访问它的属性(它们通常是通过创建的new { @style="width: 100px", ... })?

对于这种略有不同的情况,我想与您分享我的发现。在下面的解决方案中,我假设以下声明nodes

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

nodes.Add(
new
{
    Checked = false,
    depth = 1,
    id = "div_1" 
});
Run Code Online (Sandbox Code Playgroud)

现在您有一个对象列表。您如何访问对象中的属性,例如,返回Checked属性为 false的所有节点的列表?

1. 动态解决方案

C# 4.0 及更高版本中,您可以简单地转换为动态并编写:

if (nodes.Any(n => ((dynamic)n).Checked == false))
    Console.WriteLine("found a  not checked  element!");
Run Code Online (Sandbox Code Playgroud)

注:这是使用后期绑定,这意味着它只能识别在运行时,如果该对象不具有Checked财产和抛出一个RuntimeBinderException在这种情况下-因此,如果您尝试使用一个不存在的Checked2属性,您会收到以下消息在运行时间: "'<>f__AnonymousType0<bool,int,string>' does not contain a definition for 'Checked2'"

2. 用反射解决

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

背景

作为起点,我在这里找到了一个很好的答案。这个想法是通过使用反射将匿名数据类型转换为字典。字典使访问属性变得容易,因为它们的名称存储为键(您可以像 那样访问它们myDict["myProperty"])。

受上面链接中代码的启发,我创建了一个扩展类,提供GetProp,UnanonymizePropertiesUnanonymizeListItems作为扩展方法,它简化了对匿名属性的访问。使用这个类,您可以简单地执行如下查询:

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

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

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

var depth = nodes.UnanonymizeListItems()
             ?.FirstOrDefault(n => n.Contains("Checked")).GetProp("depth");
Run Code Online (Sandbox Code Playgroud)

或更短: 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 a  not checked   element!");
}
Run Code Online (Sandbox Code Playgroud)

这可以防止KeyNotFoundException在“Checked”属性不存在时发生。


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

  • 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

提示:上面的代码使用空条件运算符,自 C# 6.0 版起可用 - 如果您使用较旧的 C# 编译器(例如 C# 3.0),只需替换?.by.?[by[无处不在(并在传统上使用if语句或捕获 NullReferenceExceptions),例如

var depth = nodes.UnanonymizeListItems()
            .FirstOrDefault(n => n.Contains("Checked"))["depth"];
Run Code Online (Sandbox Code Playgroud)

如您所见,没有空条件运算符的空处理在这里会很麻烦,因为在删除它们的任何地方都必须添加空检查 - 或者在不容易找到根本原因的地方使用 catch 语句异常导致更多 - 并且难以阅读 - 代码。

如果您没有被迫使用较旧的 C# 编译器,请保持原样,因为使用 null 条件会使 null 处理变得更加容易。

注意:与动态的其他解决方案一样,此解决方案也使用后期绑定,但在这种情况下,您不会收到异常 - 如果您指的是不存在的属性,它根本找不到元素,只要当您保留空条件运算符时。

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