我有这个:
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))
...但它不起作用。
由于
答案 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
!
o
为null
,因此根本没有任何对象o
不是null
,但没有属性Foo
o
有一个属性Foo
,但其实际值恰好是null
。所以这不等同于前面的例子,但是如果你想对所有三个案例都一样对待它们可能有意义。
答案 1 :(得分:55)
如果您需要强类型的匿名类型列表,则还需要使列表成为匿名类型。最简单的方法是将一个序列(如数组)投影到一个列表中,例如
var nodes = (new[] { new { Checked = false, /* etc */ } }).ToList();
然后您就可以像访问它一样访问它:
nodes.Any(n => n.Checked);
由于编译器的工作方式,在创建列表后,以下内容也应该有效,因为匿名类型具有相同的结构,因此它们也是相同的类型。我没有编译器来验证这一点。
nodes.Add(new { Checked = false, /* etc */ });
答案 2 :(得分:12)
您可以使用Reflection迭代匿名类型的属性;看看是否有“已检查”属性,如果有,则获取其值。
类似于:
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"
});
在 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'"
。
带有反射的解决方案适用于新旧C#编译器版本。对于旧的C#版本,请考虑此答案末尾的提示。
背景
作为起点,我找到了一个很好的答案here。这个想法是通过使用反射将匿名数据类型转换为字典。通过字典,可以轻松访问属性,因为它们的名称存储为键(您可以像myDict["myProperty"]
一样访问它们)。
受以上链接中的代码的启发,我创建了一个扩展类,提供了GetProp
,UnanonymizeProperties
和UnanonymizeListItems
作为扩展方法,从而简化了对匿名属性的访问。使用此类,您可以简单地执行以下查询:
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;
...
}
...
}