Как реализовать IsAssignableFrom с помощью Mono.Сесил

#c# #mono.cecil

#c# #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) 
    amp;amp; !type.IsAbstract 
    amp;amp; !type.IsInterface;
    }
  

Как я мог бы расширить это, чтобы оно работало и для базовых классов?

Комментарии:

1. Что вы имеете normal в виду, когда ссылаетесь на класс?

2. Я имею в виду не интерфейс.

3. Изменена формулировка «обычные классы» на «базовые классы»

Ответ №1:

Основная функция изменена на

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

Комментарии:

1. Я не думаю, что сравнение MetadataToken — хорошая идея. Типы могут поступать из разных сборок.

Ответ №2:

Я согласен с комментарием Маркса:

Я не думаю, что сравнение 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
       amp;amp; 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 amp;amp; 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
            amp;amp; a.FullName == b.FullName;
    }
}