1

我有以下课程

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

但这并没有迭代引擎的属性。

4

4 回答 4

3

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
于 2013-09-03T20:39:36.777 回答
1

您可以只遍历每个属性的属性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

您可能只想评估给定命名空间中的类(例如,您最终不会捕获 的Length属性Description):

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

这将产生与前一个示例相同的输出。

于 2013-09-03T20:29:00.307 回答
1

尝试这个。您必须对一个类型的所有属性重复相同的操作以获取嵌套属性。

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);
}
于 2013-09-03T20:24:32.923 回答
0

如果您只需要原始类的属性及其第一个较低层次结构级别,那么您可以像这样创建以下属性。

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();
    }
}
于 2013-09-04T00:31:13.093 回答