简体   繁体   English

解析程序集中的类型层次结构

[英]parse type hierarchy in assembly

Consider the following types in an assembly: BusinessPartnerList, BusinessPartner, PrivateData, CompanyData, AddressList, Address 请考虑程序集中的以下类型:BusinessPartnerList,BusinessPartner,PrivateData,CompanyData,AddressList,地址

Type BusinessPartnerList 
{ 
    BusinessPartner[] 
}

Type BusinessPartner 
{
   PrivateData
   CompanyData
   AddressList
}

Type PrivateData
{
    System.String FirstName
    System.String SurName
}

Type PrivateData
{
    System.String CompanName1
    System.String CompanName2
}

Type AddressList
{
  Address[]
}

I want to generic parse the type hierarchy, and represent them in a tree eg simple nodes 我想通用解析类型层次结构,并在树中表示它们,例如简单节点

BusinessPartnerList[] BusinessPartner PrivateData CompanyData AddressList[] Address BusinessPartnerList [] BusinessPartner PrivateData CompanyData AddressList []地址

What is the best way to do this? 做这个的最好方式是什么?

Unfortunately you didn't use proper C# syntax for your sample data. 不幸的是,您没有对示例数据使用正确的C#语法。 So I have to make some assumptions: 所以我必须做一些假设:

  • Type is actually class (or struct ). Type实际上是class (或struct )。

  • The contents of the types ( BusinessPartner , PrivateData , CompanyData etc.) represent the types of some public properties. 类型的内容( BusinessPartnerPrivateDataCompanyData等)表示某些公共属性的类型。

To parse the type hierarchy you can use reflection. 要解析类型层次结构,可以使用反射。 Find all public properties of a given type and return their types. 查找给定类型的所有公共属性,然后返回其类型。 Since you only want the types you can use a HashSet which will only contain distinct types: 由于只需要类型,因此可以使用仅包含不同类型的HashSet

public static HashSet<Type> GetPropertyTypes(Type type)
{
    return new HashSet<Type>(type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                 .Select(prop => prop.PropertyType));
}

However, it seems that you don't want to get information on arrays but rather on the type of the array elements. 但是,似乎您不想获取有关数组的信息,而是获取数组元素的类型。 The same goes for lists. 列表也是如此。 So if a type implements IEnumerable<T> you want to get information on the type T : 因此,如果类型实现IEnumerable<T>您希望获取有关类型T

private static Type GetElementType(Type type)
{
    Type enumerableType = type.GetInterfaces().FirstOrDefault(IsGenericEnumerable);

    if (enumerableType != null)
    {
        Type[] genericArguments = enumerableType.GetGenericArguments();

        return genericArguments[0];
    }

    // return 'object' for a non-generic IEnumerable
    return typeof(IEnumerable).IsAssignableFrom(type) ? typeof(object) : type;
}

private static bool IsGenericEnumerable(Type type)
{
    return type.IsGenericType &&
           type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
}

Note that for the type System.String this will return char because string implements IEnumerable<char> (I will adress that later). 请注意,对于类型System.String它将返回char因为string实现了IEnumerable<char> (我将在后面进行处理)。

The .NET framework does not have a tree structure you can use out of the box. .NET框架没有可立即使用的树形结构。 So you need to implement it yourself: 因此,您需要自己实施:

public class Node<T>
{
    public Node(T value, IEnumerable<Node<T>> children)
    {
        Value = value;
        Children = children.ToList();
    }

    public T Value
    {
        get;
        private set;
    }

    public List<Node<T>> Children
    {
        get;
        private set;
    }
}

This is a very basic implementation just for demonstration purposes. 这是一个非常基本的实现,仅用于演示目的。

Instead of returning List<Type> the GetPropertyTypes method can now return Node<Type> and it should be renamed to CreateTypeNode : 现在, GetPropertyTypes方法可以返回Node<Type>而不是返回List<Type> ,而应将其重命名为CreateTypeNode

public static Node<Type> CreateTypeNode(Type type)
{
    var children = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                       .Select(prop => GetElementType(prop.PropertyType))
                       .Select(CreateTypeNode);

    return new Node<Type>(type, children);
}

This method uses recursion to create the full tree for the given type. 此方法使用递归为给定类型创建完整树。

There is still a problem: What if type A references type B and vice versa? 仍然存在一个问题:如果类型A引用类型B ,反之亦然呢? This would end up in an infinite recursive loop. 这将最终导致无限递归循环。 And also: if a type has already been visited there is no need to do that again. 而且:如果已经访问过某种类型,则无需再次执行该操作。

What we need is a cache for the types that have been visited. 我们需要的是已访问类型的缓存。 If a type is in the cache we use the information from the cache: 如果缓存中有类型,我们将使用缓存中的信息:

private static readonly Dictionary<Type, Node<Type>> _visitedTypes = new Dictionary<Type, Node<Type>>();

public static Node<Type> CreateTypeNode(Type type)
{
    Node<Type> node;
    if (_visitedTypes.TryGetValue(type, out node))
    {
        return node;
    }

    // add the key to the cache to prevent infinite recursion; the value will be set later
    // if this type will be found again in a recursive call CreateTypeNode returns null
    // (null will be filtered out then)
    _visitedTypes.Add(type, null);

    var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);

    var types = new HashSet<Type>(properties.Select(prop => GetElementType(prop.PropertyType)));

    var children = types.Select(CreateTypeNode).Where(n => n != null);

    node = new Node<Type>(type, children);
    _visitedTypes[type] = node;

    return node;
}

I you don't want the string type to be reported as char (because string implements IEnumerable<char> ) you can just add a node for string to the cache before you call GetOrCreateTypeNode for the first time: 我不希望将string类型报告为char (因为string实现了IEnumerable<char> ),您可以在第一次调用GetOrCreateTypeNode之前将string的节点添加到缓存中:

_visitedTypes.Add(typeof(string), new Node<Type>(typeof(string), new List<Node<Type>>()));

Then check the cache in the GetElementType method: 然后在GetElementType方法中检查缓存:

private static Type GetElementType(Type type)
{
    if (_visitedTypes.ContainsKey(type))
    {
        return type;
    }

    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM