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)
注意,通过使用?.我们导致生成的v是null在三种不同的情况!
o是的null,所以没有任何对象o是非,null但没有财产Fooo有一个属性,Foo但它的真正价值恰好是null.所以这不等同于前面的例子,但如果你想对所有三种情况都一视同仁,这可能是有意义的.
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)
接受的答案正确地描述了应该如何声明列表,并且强烈建议在大多数情况下使用。
但是我遇到了一个不同的场景,它也涵盖了所提出的问题。如果您必须使用现有的对象列表,例如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的所有节点的列表?
在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'"。
带有反射的解决方案适用于旧的和新的 C# 编译器版本。对于旧的 C# 版本,请注意本答案末尾的提示。
背景
作为起点,我在这里找到了一个很好的答案。这个想法是通过使用反射将匿名数据类型转换为字典。字典使访问属性变得容易,因为它们的名称存储为键(您可以像 那样访问它们myDict["myProperty"])。
受上面链接中代码的启发,我创建了一个扩展类,提供GetProp,UnanonymizeProperties和UnanonymizeListItems作为扩展方法,它简化了对匿名属性的访问。使用这个类,您可以简单地执行如下查询:
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 中的字符串引用该属性,因此可以对其进行参数化。