被接受的答案正确描述了列表应该如何声明,并且在大多数情况下强烈推荐使用。
但是我遇到了另一种情况,也涵盖了提出的问题。
如果您必须使用现有的对象列表,比如 ViewData["htmlAttributes"]
在 MVC 中怎么办?如何访问其属性(通常通过 new { @style="width: 100px", ... }
创建)?
对于这种稍微不同的情况,我想与您分享我发现的内容。
在下面的解决方案中,我假设以下声明适用于nodes
:
List<object> nodes = new List<object>();
nodes.Add(
new
{
Checked = false, depth = 1, id = "div_1"
});
nodes.Add(
new
{
Checked = true, depth = 2, id = "div_2"
});
现在你有一个对象列表。如何访问对象内的属性,例如返回所有节点中
Checked
属性为false的节点列表?
1. 使用dynamic解决方案
在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>'不包含 'Checked2' 的定义”。
2. 反射解决方案
反射解决方案适用于旧版和新版 C# 编译器。对于旧版 C# 版本,请参考本答案末尾的提示。
背景
作为起点,我在这里找到了一个很好的答案here。其思路是通过使用反射将匿名数据类型转换为字典。由于属性名称存储为键(您可以像myDict["myProperty"]
这样访问它们),因此使用字典可以轻松访问属性。
受到上面链接中的代码启发,我创建了一个扩展类,提供了
GetProp
、
UnanonymizeProperties
和
UnanonymizeListItems
作为扩展方法,以简化对匿名属性的访问。使用这个类,你可以简单地进行如下查询:
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"属性的第一个对象,并返回其属性"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 a not checked element!");
}
这样做可以防止出现“KeyNotFoundException”异常,如果“Checked”属性不存在的话。
下面的类包含以下扩展方法:
UnanonymizeProperties
:用于将对象中包含的属性“去匿名化”。此方法使用反射。它将对象转换为包含属性及其值的字典。
UnanonymizeListItems
:用于将对象列表转换为包含属性的字典列表。可以选择在此之前包含一个用于过滤的lambda表达式。
GetProp
:用于返回与给定属性名称匹配的单个值。允许将不存在的属性视为null值(true),而不是抛出KeyNotFoundException异常(false)。
对于上述示例,只需添加下面的扩展类即可。
public static class AnonymousTypeExtensions
{
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;
}
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;
}
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版本以后提供的null条件运算符 - 如果你正在使用旧版的C#编译器(例如C# 3.0),只需将“?. ”替换为“。”,将“?[”替换为“[”,并在所有地方进行空值处理(通过使用if语句或捕获NullReferenceExceptions),例如。
var depth = nodes.UnanonymizeListItems()
.FirstOrDefault(n => n.Contains("Checked"))["depth"];
正如您所见,如果没有空值条件运算符,处理空值将变得繁琐,因为无论您在何处删除它们,都必须添加空值检查 - 或者在很难找到异常根本原因的地方使用catch语句,从而导致更多且难以阅读的代码。
如果您不被迫使用旧版C#编译器,请保持原样,因为使用空值条件运算符可以使空值处理变得更容易。
注意:像使用dynamic的其他解决方案一样,此解决方案也使用了后期绑定,但是在这种情况下,如果引用的是不存在的属性,则不会抛出异常 - 只要保留null-conditional运算符,它就不会找到该元素。
对于某些应用程序可能有用的是,在解决方案2中,属性通过字符串引用,因此可以进行参数化。
ExpandoObject
,例如dynamic o = new ExpandoObject(); o.Foo = "Something";
- Daniel EarwickerRuntimeBinder.RuntimeBinderException
异常,它声称 'object' 不包含定义some-property
,那么你需要在你的AssemblyInfo.cs
中添加一个InternalsVisibleTo
属性,请参考 https://juristr.com/blog/2013/08/object-does-not-contain-definition/ 了解更多信息。 - Jijie Chen