4

I have a type Type that I wish to search an assembly for derived types.

I'm trying to use Mono.Cecil to prescan the assembly for performance reasons. Scanning and loading all assemblies is taking too long and its been suggested that cecil is much faster to do a prescan with as only a fraction of the assemblies available will having matching types.

So far I have the below which works only for interfaces.

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

How could I extend this to work for base classes as well?

bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217

2 Answers2

2

The main function is changed to be

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

}

and the supporting Mono.Cecil code is where IsAssignableFrom is defined is below

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;
      }
   }
}
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • I do not think comparing `MetadataToken` is a good idea. The types may come from different assemblies. – mark Jun 21 '21 at 02:57
1

I agree with marks comment:

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

The MetadataToken seems to only be unique within the same assembly. I had the problem with two completetly different types from different assemblies having the same MetadataToken. My suggestion is to also check equlity for the full name. Comparing the full name is a quite good check by itself. Together with MetadataToken I think the compare is foolproof.

Updated solution:

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