迭代属性和嵌套的一级属性
本文关键字:属性 一级 嵌套 迭代 | 更新日期: 2023-09-27 18:30:01
我有以下类
public class Car
{
public int CarId { get; set;}
public string Description { get; set;}
public Engine Engine { get; set;}
}
public class Engine
{
public int EngineId { get; set;}
public int Description { get; set;}
}
现在我想迭代Car中的所有属性和Engine中的所有特性,我不想硬编码属性名称"Car or Engine"
获取Car的所有属性的示例,其中obj
是Car的实例。
var properties = obj.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
但这不会迭代Engine的属性。
FlattenHierarchy
不执行您认为它会执行的操作,而是遵循静态成员的继承层次结构。
如果你想获得对象的子属性,你需要自己做:
static IEnumerable<PropertyInfo> FlattenProperties(Type type)
{
// Assumption #1: you do not want "simple" types enumerated
if (!type.IsClass)
return Enumerable.Empty<PropertyInfo>();
// Assumption #2: you want to ignore "the usual suspects"
if (type.Namespace != null && type.Namespace.StartsWith("System"))
return Enumerable.Empty<PropertyInfo>();
// Assumption #3: your class hierarchy won't destroy recursion
// Assumption #4: you simply want the PropertyInfo
return type.GetProperties(BindingFlags.FlattenHierarchy
| BindingFlags.Instance
| BindingFlags.Public)
.SelectMany(pi => new[] { pi }
.Concat(FlattenProperties(pi.PropertyType)));
}
如果这在代码中使用,您(a)知道递归的深度,并且(b)有方法更改代码,我建议为这些类型/属性创建基类、接口或属性。
// Replace Assumptions #1 and #2 above with this:
// Assumption #5: given interface ISomething { }
if (!typeof(ISomething).IsAssignableFrom(type))
return Enumerable.Empty<PropertyInfo>();
如果您需要"属性树"(即假设#4不正确):
static IEnumerable<IEnumerable<PropertyInfo>> FlattenProperties(
Type type,
IEnumerable<PropertyInfo> ancestors = null)
{
// change to Assumptions #1/#2 or #5 to yield break
// ...
ancestors = ancestors ?? Enumerable.Empty<PropertyInfo>();
var properties = type.GetProperties(
BindingFlags.FlattenHierarchy
| BindingFlags.Instance
| BindingFlags.Public);
foreach (var property in properties)
{
// again, Assumption #3: your class hierarchy won't destroy recursion
// Assumption #6: you actually want the initial nested property too
yield return ancestors.Concat(new[] { property });
foreach (var nested in FlattenProperties(
property.PropertyType,
ancestors.Concat(new [] { property })))
{
yield return nested;
}
}
}
在第二种情况下,它产生类似于的输出
// foreach (var tree in FlattenProperties(typeof(Car)))
// {
// Console.WriteLine("{0}", String.Join(".", tree.Select(pi => pi.Name)));
// }
CarId
Description
Engine
Engine.EngineId
Engine.Description
试试这个。您必须对一个类型的所有属性重复相同的操作才能获得嵌套属性。
var properties = obj.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
foreach(var pi in properties)
{
var nestedProperties = pi.PropertyType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public);
}
您只需迭代每个属性的PropertyType
的属性,就像您获取一级属性一样。
下面是一个使用Linq:的快速而肮脏的例子
var properties =
from p1 in obj.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public)
from p2 in p1.PropertyType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public).DefaultIfEmpty()
select new { OuterProperty = p1, InnerProperty = p2 };
foreach(var prop in properties)
{
Console.WriteLine(prop.OuterProperty.Name + (prop.InnerProperty != null ? "." + prop.InnerProperty.Name : ""));
}
产生输出:
CarId
Description.Chars
Description.Length
Engine.EngineId
Engine.Description
您可能只想评估给定命名空间中的类(例如,这样就不会捕获Description
的Length
属性):
var properties =
from p1 in obj.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public)
from p2 in p1.PropertyType.Namespace == "MyNamespace"
? p1.PropertyType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public).DefaultIfEmpty()
: new PropertyInfo[] { null }
select new { OuterProperty = p1, InnerProperty = p2 };
产生输出:
CarId
Description
Engine.EngineId
Engine.Description
或者,如果你定义了一个特定的属性来标记你想要遍历的属性,也许会更优雅:
[AttributeUsage(AttributeTargets.Property)]
public class TraversableAttribute: Attribute { }
public class Car
{
public int CarId { get; set;}
public string Description { get; set;}
[Traversable]
public Engine Engine { get; set;}
}
...
var properties =
from p1 in obj.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public)
from p2 in p1.GetCustomAttributes(typeof(TraversableAttribute), true).Length > 0
? p1.PropertyType.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public).DefaultIfEmpty()
: new PropertyInfo[] { null }
select new { OuterProperty = p1, InnerProperty = p2 };
这将产生与上一个示例相同的输出。
如果您只需要原始类及其第一个较低层次结构级别的属性,那么您可以像这样创建以下属性。
public class BrowsableAttribute : Attribute { }
现在,只需装饰要浏览属性的类,在您的情况下,这将是Engine类。
[Browsable]
class Engine
{
public int EngineId { get; set; }
public int Description { get; set; }
}
现在,您所需要做的就是使用以下扩展方法。
public static class TypeExtensions
{
public static void BrowseProperties(this Type type)
{
var h1 = typeof(Car).GetProperties().ToList();
var h2 = h1.Where(x => x.PropertyType.GetCustomAttributes(true).OfType<BrowsableAttribute>().Any());
h2.ToList().ForEach(x => h1.AddRange(x.PropertyType.GetProperties()));
h1.ForEach(x => Console.WriteLine(x.Name));
}
}
这将产生所需的结果。要查看此信息,请执行以下代码行。
class Program
{
static void Main()
{
typeof(Engine).BrowseProperties();
Console.Read();
}
}