Search code examples
c#mono.cecil

How to implement IsAssignableFrom with Mono.Cecil


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?


Solution

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