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

本文关键字:类型 层次结构 集中 程序集 程序 | 更新日期: 2023-09-27 18:34:10

考虑程序集中的以下类型:业务合作伙伴列表、业务伙伴、私有数据、公司数据、地址列表、地址

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[]
}

我想泛型解析类型层次结构,并在树中表示它们,例如简单节点

商业伙伴名单[] 商业伙伴 私人数据 公司数据 地址列表[] 地址

最好的方法是什么?

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

遗憾的是,

您没有对示例数据使用正确的 C# 语法。所以我必须做一些假设:

  • Type实际上是class(或struct(。

  • 类型(BusinessPartnerPrivateDataCompanyData等(的内容表示某些公共属性的类型。

若要分析类型层次结构,可以使用反射。查找给定类型的所有公共属性并返回其类型。由于您只需要这些类型,因此您可以使用仅包含不同类型的HashSet

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

但是,您似乎不想获取有关数组的信息,而是要获取有关数组元素类型的信息。列表也是如此。因此,如果一个类型实现了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<>);
}

请注意,对于类型 System.String这将返回char string实现IEnumerable<char>(我将在稍后解决这个问题(。

.NET 框架没有可以开箱即用的树结构。所以你需要自己实现它:

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

这是一个非常基本的实现,仅用于演示目的。

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

此方法使用递归为给定类型创建完整树。

仍然存在一个问题:如果类型 A 引用类型 B,反之亦然怎么办?这将最终进入无限递归循环。而且:如果已经访问过某个类型,则无需再次执行此操作。

我们需要的是已访问类型的缓存。如果类型在缓存中,我们使用缓存中的信息:

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

我不希望将string类型报告为char(因为string实现IEnumerable<char>(,您可以在第一次调用GetOrCreateTypeNode之前将用于string的节点添加到缓存中:

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

然后在GetElementType方法中检查缓存:

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