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

我有这个:

List nodes = new List();


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



我想知道我是否可以获取匿名对象的“ Checked”属性。我不确定这是否可行。试过这样做:

if (nodes.Any(n => n["Checked"] == false))... 但是没有用。

谢谢

142065 次浏览

如果想要一个匿名类型的强类型列表,还需要将该列表设置为匿名类型。最简单的方法是将一个序列(如数组)投射到一个列表中,例如。

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

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

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

由于编译器的工作方式,一旦创建了列表,下面的代码也应该可以工作,因为匿名类型具有相同的结构,所以它们也是相同的类型。不过我手头没有编译器来验证这一点。

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

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

所以这不等同于前面的例子,但是如果您希望对所有三种情况一视同仁,那么这可能是有意义的。

要使用 充满活力读取单元测试中匿名类型的属性,您需要告诉项目的编译器服务使程序集在内部对测试项目可见。可以将以下内容添加到项目中(。Proj)档案。有关详细信息,请参阅 这个链接

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Name of your test project</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

可以使用反射遍历匿名类型的属性; 查看是否有“ Checked”属性,然后获取其值。

看看这篇博文: 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!");
}
}

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

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

对于这个稍微不同的场景,我想与你们分享我的发现。 在下面的解决方案中,我假设 nodes的声明如下:

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


nodes.Add(
new
{
Checked = false,
depth = 1,
id = "div_1"
});

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

1. 动态解决方案

C # 4.0或更高版本中,您可以简单地强制转换为 Dynamic 并编写:

if (nodes.Any(n => ((dynamic)n).Checked == false))
Console.WriteLine("found a  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 a  not checked  element!");
}

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

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

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

提示: 上面的代码使用的是从 C # 版本6.0开始就可以使用的 无条件的操作符——如果您使用的是 旧的 C # 编译器(例如 C # 3.0) ,,那么只需将 ?.替换为 .,将 ?[替换为 [(并且通常使用 if语句执行 null 处理或者捕获 NullReferenceException) ,例如。

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

正如你所看到的,没有空条件操作符的空处理在这里会很麻烦,因为无论你在哪里删除它们,你都必须添加一个空检查——或者使用 catch 语句,这样就不容易找到异常的根本原因,从而导致更多代码难以阅读。

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

注意: 与另一个使用 Dynamic 的解决方案一样,这个解决方案也使用后期绑定,但是在这种情况下,您不会得到异常-如果您引用的是一个不存在的属性,它就不会找到元素,只要您保留 无条件的操作符。

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

最近,我在.NET 3.5中遇到了同样的问题(没有动态版本)。 我是这样解决的:

// pass anonymous object as argument
var args = new { Title = "Find", Type = typeof(FindCondition) };


using (frmFind f = new frmFind(args))
{
...
...
}

改编自堆栈溢出的某处:

// 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;


...
}
...
}