如何使用 Mono.Cecil 实现 IsAssignableFrom

How to implement IsAssignableFrom with Mono.Cecil

我有一个类型 Type,我希望在程序集中搜索派生类型。

出于性能原因,我正在尝试使用 Mono.Cecil 预扫描程序集。扫描和加载所有程序集花费的时间太长,建议使用 cecil 进行预扫描要快得多,因为只有一小部分可用程序集具有匹配类型。

到目前为止,我有以下仅适用于接口的内容。

    private static IEnumerable<Type> MatchingTypesFromDll<TParent>(string dllPath)
    {
        var type = typeof(TParent);
        if (!type.IsInterface)
            throw new Exception("Only interfaces supported");
        try
        {

            var assDef = Mono.Cecil.AssemblyDefinition.ReadAssembly(dllPath);
            var types = assDef.Modules.SelectMany(m => m.GetTypes());
            if (types.Any(t => t.Interfaces.Any(i=>i.FullName == type.FullName)))
            {
                var assembly = Assembly.LoadFrom(dllPath);
                return assembly
                    .GetExportedTypes()
                    .Where(TypeSatisfies<TParent>);
            }
            else
            {
                return new Type[] {};
            }
        }
        catch (Exception e)
        {
            return new Type[] { };
        }

    }

    private static bool TypeSatisfies<TParent>(Type type)
    {
        return typeof (TParent).IsAssignableFrom(type) 
    && !type.IsAbstract 
    && !type.IsInterface;
    }

我如何扩展它以使其也适用于基础 类?

主要功能改为

private static IEnumerable<Type> MatchingTypesFromDll<TBaseType>(string dllPath)
{
   var type = typeof(TBaseType);
   try
   {
      var hasTypes = Mono.Cecil.AssemblyDefinition
          .ReadAssembly(dllPath)
          .Modules
          .Any
          (m =>
           {
              var td = m.Import(type).Resolve();
              return m.GetTypes().Any(t => td.IsAssignableFrom(t));
           });

      if (hasTypes)
      {
          var assembly = Assembly.LoadFrom(dllPath);
          return assembly
         .GetExportedTypes()
         .Where(TypeSatisfies<TBaseType>);
      }
      else
      {
          return new Type[] {};
      }
   }
   catch (Exception)
   {
      return new Type[] { };
   }

}

支持 Mono.Cecil 代码是下面定义 IsAssignableFrom 的地方

static internal class TypeDefinitionExtensions
{
   /// <summary>
   /// Is childTypeDef a subclass of parentTypeDef. Does not test interface inheritance
   /// </summary>
   /// <param name="childTypeDef"></param>
   /// <param name="parentTypeDef"></param>
   /// <returns></returns>
   public static bool IsSubclassOf(this TypeDefinition childTypeDef, TypeDefinition parentTypeDef) => 
      childTypeDef.MetadataToken 
          != parentTypeDef.MetadataToken 
          && childTypeDef
         .EnumerateBaseClasses()
         .Any(b => b.MetadataToken == parentTypeDef.MetadataToken);

   /// <summary>
   /// Does childType inherit from parentInterface
   /// </summary>
   /// <param name="childType"></param>
   /// <param name="parentInterfaceDef"></param>
   /// <returns></returns>
   public static bool DoesAnySubTypeImplementInterface(this TypeDefinition childType, TypeDefinition parentInterfaceDef)
   {
      Debug.Assert(parentInterfaceDef.IsInterface);
      return childType
     .EnumerateBaseClasses()
     .Any(typeDefinition => typeDefinition.DoesSpecificTypeImplementInterface(parentInterfaceDef));
   }

   /// <summary>
   /// Does the childType directly inherit from parentInterface. Base
   /// classes of childType are not tested
   /// </summary>
   /// <param name="childTypeDef"></param>
   /// <param name="parentInterfaceDef"></param>
   /// <returns></returns>
   public static bool DoesSpecificTypeImplementInterface(this TypeDefinition childTypeDef, TypeDefinition parentInterfaceDef)
   {
      Debug.Assert(parentInterfaceDef.IsInterface);
      return childTypeDef
     .Interfaces
     .Any(ifaceDef => DoesSpecificInterfaceImplementInterface(ifaceDef.Resolve(), parentInterfaceDef));
   }

   /// <summary>
   /// Does interface iface0 equal or implement interface iface1
   /// </summary>
   /// <param name="iface0"></param>
   /// <param name="iface1"></param>
   /// <returns></returns>
   public static bool DoesSpecificInterfaceImplementInterface(TypeDefinition iface0, TypeDefinition iface1)
   {
     Debug.Assert(iface1.IsInterface);
     Debug.Assert(iface0.IsInterface);
     return iface0.MetadataToken == iface1.MetadataToken || iface0.DoesAnySubTypeImplementInterface(iface1);
   }

   /// <summary>
   /// Is source type assignable to target type
   /// </summary>
   /// <param name="target"></param>
   /// <param name="source"></param>
   /// <returns></returns>
   public static bool IsAssignableFrom(this TypeDefinition target, TypeDefinition source) 
  => target == source 
     || target.MetadataToken == source.MetadataToken 
     || source.IsSubclassOf(target)
     || target.IsInterface && source.DoesAnySubTypeImplementInterface(target);

   /// <summary>
   /// Enumerate the current type, it's parent and all the way to the top type
   /// </summary>
   /// <param name="klassType"></param>
   /// <returns></returns>
   public static IEnumerable<TypeDefinition> EnumerateBaseClasses(this TypeDefinition klassType)
   {
      for (var typeDefinition = klassType; typeDefinition != null; typeDefinition = typeDefinition.BaseType?.Resolve())
      {
         yield return typeDefinition;
      }
   }
}

我同意标记评论:

I do not think comparing MetadataToken is a good idea. The types may come from different assemblies.

MetadataToken 似乎只在同一个程序集中是唯一的。我遇到了来自具有相同 MetadataToken 的不同程序集的两种完全不同类型的问题。我的建议是也检查全名的平等性。比较全名本身就是一个很好的检查。与 MetadataToken 一起,我认为比较是万无一失的。

更新的解决方案:

internal static class TypeDefinitionExtensions
{
    /// <summary>
    /// Is childTypeDef a subclass of parentTypeDef. Does not test interface inheritance
    /// </summary>
    /// <param name="childTypeDef"></param>
    /// <param name="parentTypeDef"></param>
    /// <returns></returns>
    public static bool IsSubclassOf(this TypeDefinition childTypeDef, TypeDefinition parentTypeDef) =>
       childTypeDef.MetadataToken != parentTypeDef.MetadataToken
       && childTypeDef.EnumerateBaseClasses().Any(b => Equals(b, parentTypeDef));

    /// <summary>
    /// Does childType inherit from parentInterface
    /// </summary>
    /// <param name="childType"></param>
    /// <param name="parentInterfaceDef"></param>
    /// <returns></returns>
    public static bool DoesAnySubTypeImplementInterface(this TypeDefinition childType, TypeDefinition parentInterfaceDef)
    {
        Debug.Assert(parentInterfaceDef.IsInterface);

        return
            childType
            .EnumerateBaseClasses()
            .Any(typeDefinition => typeDefinition.DoesSpecificTypeImplementInterface(parentInterfaceDef));
    }

    /// <summary>
    /// Does the childType directly inherit from parentInterface. Base
    /// classes of childType are not tested
    /// </summary>
    /// <param name="childTypeDef"></param>
    /// <param name="parentInterfaceDef"></param>
    /// <returns></returns>
    public static bool DoesSpecificTypeImplementInterface(this TypeDefinition childTypeDef, TypeDefinition parentInterfaceDef)
    {
        Debug.Assert(parentInterfaceDef.IsInterface);
        return childTypeDef
       .Interfaces
       .Any(ifaceDef => DoesSpecificInterfaceImplementInterface(ifaceDef.InterfaceType.Resolve(), parentInterfaceDef));
    }

    /// <summary>
    /// Does interface iface0 equal or implement interface iface1
    /// </summary>
    /// <param name="iface0"></param>
    /// <param name="iface1"></param>
    /// <returns></returns>
    public static bool DoesSpecificInterfaceImplementInterface(TypeDefinition iface0, TypeDefinition iface1)
    {
        Debug.Assert(iface1.IsInterface);
        Debug.Assert(iface0.IsInterface);
        return Equals(iface0, iface1) || iface0.DoesAnySubTypeImplementInterface(iface1);
    }

    /// <summary>
    /// Is source type assignable to target type
    /// </summary>
    /// <param name="target"></param>
    /// <param name="source"></param>
    /// <returns></returns>
    public static bool IsAssignableFrom(this TypeDefinition target, TypeDefinition source)
   => target == source
      || Equals(target, source)
      || source.IsSubclassOf(target)
      || target.IsInterface && source.DoesAnySubTypeImplementInterface(target);

    /// <summary>
    /// Enumerate the current type, it's parent and all the way to the top type
    /// </summary>
    /// <param name="classType"></param>
    /// <returns></returns>
    public static IEnumerable<TypeDefinition> EnumerateBaseClasses(this TypeDefinition classType)
    {
        for (var typeDefinition = classType; typeDefinition != null; typeDefinition = typeDefinition.BaseType?.Resolve())
        {
            yield return typeDefinition;
        }
    }

    public static bool Equals(TypeDefinition a, TypeDefinition b)
    {
        return
            a.MetadataToken == b.MetadataToken
            && a.FullName == b.FullName;
    }
}