Подтвердить что ты не робот

Как найти минимальный ковариантный тип для наилучшего соответствия между двумя типами?

Там IsAssignableFrom метод возвращает логическое значение, указывает, является ли один тип назначаемым из другого типа.

Как мы можем не только проверить, могут ли они присваиваться от или до друг друга, но также знать тип минимальный ковариационный для наилучшего соответствия?

Рассмотрим следующий пример (С# 4.0)

  • код

    // method body of Func is irrelevant, use default() instead
    Func<char[]> x = default(Func<char[]>);
    Func<int[]> y = default(Func<int[]>);
    
    Func<Array> f = default(Func<Array>);
    Func<IList> g = default(Func<IList>);
    
    g=x;
    g=y;
    
    y=x; // won't compile
    x=y; // won't compile
    
    // following two are okay; Array is the type for the covariance
    f=x; // Array > char[] -> Func<Array> > Func<char[]> 
    f=y; // Array > int[] -> Func<Array> > Func<int[]> 
    
    // following two are okay; IList is the interface for the covariance
    g=x;
    g=y;
    

В приведенном выше примере, что найти - это тип между char[] и int[].

4b9b3361

Ответ 1

update:

Оказывается, FindInterfaceWith может быть упрощено, а построение иерархии типа flatten становится излишним, поскольку базовые классы не обязательно задействованы, если мы принимаем этот тип во внимание, когда он является интерфейсом; поэтому я добавил метод расширения GetInterfaces(bool). Поскольку мы можем сортировать интервалы по правилам покрытия, сортированные пересечения интерфейсов являются кандидатами. Если все они одинаково хороши, я сказал, что ни один из них не считается лучшим. Если это не так, то лучший должен покрыть один из других; и потому, что они отсортированы, такой вид отношений должен существовать в правильном большинстве двух интерфейсов в массиве, чтобы обозначить, что существует лучший интерфейс, который является наиболее конкретным.


Код можно упростить, используя Linq; но в моем сценарии я должен как можно меньше уменьшить требования к ссылкам и пространствам имен.

  • Код

    using System;
    
    public static class TypeExtensions {
        static int CountOverlapped<T>(T[] ax, T[] ay) {
            return IntersectPreserveOrder(ay, ax).Length;
        }
    
        static int CountOccurrence(Type[] ax, Type ty) {
            var a = Array.FindAll(ax, x => Array.Exists(x.GetInterfaces(), tx => tx.Equals(ty)));
            return a.Length;
        }
    
        static Comparison<Type> GetCoverageComparison(Type[] az) {
            return (tx, ty) => {
                int overlapped, occurrence;
                var ay = ty.GetInterfaces();
                var ax = tx.GetInterfaces();
    
                if(0!=(overlapped=CountOverlapped(az, ax).CompareTo(CountOverlapped(az, ay)))) {
                    return overlapped;
                }
    
                if(0!=(occurrence=CountOccurrence(az, tx).CompareTo(CountOccurrence(az, ty)))) {
                    return occurrence;
                }
    
                return 0;
            };
        }
    
        static T[] IntersectPreserveOrder<T>(T[] ax, T[] ay) {
            return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))>=0);
        }
    
        /*
        static T[] SubtractPreserveOrder<T>(T[] ax, T[] ay) {
            return Array.FindAll(ax, x => Array.FindIndex(ay, y => y.Equals(x))<0);
        }
    
        static Type[] GetTypesArray(Type typeNode) {
            if(null==typeNode) {
                return Type.EmptyTypes;
            }
    
            var baseArray = GetTypesArray(typeNode.BaseType);
            var interfaces = SubtractPreserveOrder(typeNode.GetInterfaces(), baseArray);
            var index = interfaces.Length+baseArray.Length;
            var typeArray = new Type[1+index];
            typeArray[index]=typeNode;
            Array.Sort(interfaces, GetCoverageComparison(interfaces));
            Array.Copy(interfaces, 0, typeArray, index-interfaces.Length, interfaces.Length);
            Array.Copy(baseArray, typeArray, baseArray.Length);
            return typeArray;
        }
        */
    
        public static Type[] GetInterfaces(this Type x, bool includeThis) {
            var a = x.GetInterfaces();
    
            if(includeThis&&x.IsInterface) {
                Array.Resize(ref a, 1+a.Length);
                a[a.Length-1]=x;
            }
    
            return a;
        }
    
        public static Type FindInterfaceWith(this Type type1, Type type2) {
            var ay = type2.GetInterfaces(true);
            var ax = type1.GetInterfaces(true);
            var types = IntersectPreserveOrder(ax, ay);
    
            if(types.Length<1) {
                return null;
            }
    
            Array.Sort(types, GetCoverageComparison(types));
            var type3 = types[types.Length-1];
    
            if(types.Length<2) {
                return type3;
            }
    
            var type4 = types[types.Length-2];
            return Array.Exists(type3.GetInterfaces(), x => x.Equals(type4)) ? type3 : null;
        }
    
        public static Type FindBaseClassWith(this Type type1, Type type2) {
            if(null==type1) {
                return type2;
            }
    
            if(null==type2) {
                return type1;
            }
    
            for(var type4 = type2; null!=type4; type4=type4.BaseType) {
                for(var type3 = type1; null!=type3; type3=type3.BaseType) {
                    if(type4==type3) {
                        return type4;
                    }
                }
            }
    
            return null;
        }
    
        public static Type FindAssignableWith(this Type type1, Type type2) {
            var baseClass = type2.FindBaseClassWith(type1);
    
            if(null==baseClass||typeof(object)==baseClass) {
                var @interface = type2.FindInterfaceWith(type1);
    
                if([email protected]) {
                    return @interface;
                }
            }
    
            return baseClass;
        }
    }
    

Есть два рекурсивных метода; один - FindInterfaceWith, другой - важный метод GetTypesArray, поскольку уже существует метод с именем GetTypeArray класса Type с другим использованием.

Он работает как метод Akim при условии GetClassHierarchy; но в этой версии он создает массив вроде:

  • вывод иерархии

    a[8]=System.String
    a[7]=System.Collections.Generic.IEnumerable`1[System.Char]
    a[6]=System.Collections.IEnumerable
    a[5]=System.ICloneable
    a[4]=System.IComparable
    a[3]=System.IConvertible
    a[2]=System.IEquatable`1[System.String]
    a[1]=System.IComparable`1[System.String]
    a[0]=System.Object
    

Как мы знаем, они находятся в определенном порядке, и именно так оно и работает. Массив GetTypesArray, построенный на самом деле, является сплюснутым деревом. Массив фактически находится в модели следующим образом:

  • диаграмма

    rFbtV.png

    Обратите внимание, что отношение реализации некоторых интерфейсов, таких как IList<int> реализует ICollection<int>, не связано с строками на этой диаграмме.

Интерфейсы в возвращаемом массиве сортируются по Array.Sort с правилами упорядочения, предоставляемыми GetCoverageComparison.

Можно упомянуть, например, возможность использования нескольких интерфейсов не только один раз в некоторых ответах (например, < this]); и я определил способ их решения:

  • note

    • GetInterfaces метод не возвращает интерфейсы в определенном порядке, например, в алфавитном порядке или порядке объявления. Ваш код не должен зависеть от порядка возврата интерфейсов, поскольку этот порядок меняется.

    • Из-за рекурсии базовые классы всегда упорядочены.

    • Если два интерфейса имеют одинаковое покрытие, ни один из них не будет считаться приемлемым.

      Предположим, что эти интерфейсы определены (или классы просто прекрасны):

      public interface IDelta {
      }
      
      public interface ICharlie {
      }
      
      public interface IBravo: IDelta, ICharlie {
      }
      
      public interface IAlpha: IDelta, ICharlie {
      }
      

      то какой из них лучше для назначения IAlpha и IBravo? В этом случае FindInterfaceWith просто возвращает null.

В вопросе [Как найти наименьший присваиваемый тип в двух типах (дубликат)?], я заявил:

  • неправильный вывод

    Если это предположение было правильным, то FindInterfaceWith становится избыточным методом; из-за единственной разницы между FindInterfaceWith и FindAssignableWith является:

    FindInterfaceWith возвращает null, если был лучший выбор класса; а FindAssignableWith возвращает точный класс напрямую.

Однако теперь мы можем посмотреть на метод FindAssignableWith, он должен вызывать другие два метода, основываясь на исходном предположении. Парадоксальная ошибка просто исчезла волшебным образом.


О правилах сопоставления с охватом интерфейсов упорядочения, в делегате GetCoverageComparison, я использую:

  • двойные правила

    • сравнивайте два интерфейса в массиве исходных интерфейсов, каждый из которых покрывает количество других в источнике, вызывая CountOverlapped

    • Если правило 1 не различает их (возвращает 0), вторичное упорядочение - это вызов CountOccurrence для определения того, что унаследовано больше раз другими, а затем сравнение

      два правила эквивалентны запросу Linq:

      interfaces=(
          from it in interfaces
          let order1=it.GetInterfaces().Intersect(interfaces).Count()
          let order2=(
              from x in interfaces
              where x.GetInterfaces().Contains(it)
              select x
              ).Count()
          orderby order1, order2
          select it
          ).ToArray();
      

      FindInterfaceWith выполнит, возможно, рекурсивный вызов, чтобы выяснить, является ли этот интерфейс достаточным для распознавания как наиболее общий интерфейс или просто другое отношение, подобное IAlpha и IBravo.

И о методе FindBaseClassWith, то, что он возвращает, отличается от исходного предположения о том, что любой параметр имеет значение null, а затем возвращает null. Он фактически возвращает другой аргумент, переданный в.

Это связано с вопросом [Что должен возвращать метод` FindBaseClassWith`?] о цепочке методов FindBaseClassWith. В текущей реализации мы можем назвать это следующим образом:

  • цепочка методов

    var type=
        typeof(int[])
            .FindBaseClassWith(null)
            .FindBaseClassWith(null)
            .FindBaseClassWith(typeof(char[]));
    

    Он вернет typeof(Array); благодаря этой функции, мы можем даже позвонить

    var type=
        typeof(String)
            .FindAssignableWith(null)
            .FindAssignableWith(null)
            .FindAssignableWith(typeof(String));
    

    То, что мы не можем сделать с моей реализацией, - это вызвать FindInterfaceWith, как указано выше, из-за возможности таких отношений, как IAlpha и IBravo.

В некоторых ситуациях я тестировал код, вызывая FindAssignableWith в качестве показанных примеров:

  • вывод назначаемых типов

    (Dictionary`2, Dictionary`2) = Dictionary`2
    (List`1, List`1) = IList
    (Dictionary`2, KeyValuePair`2) = Object
    (IAlpha, IBravo) = <null>
    (IBravo, IAlpha) = <null>
    (ICollection, IList) = ICollection
    (IList, ICollection) = ICollection
    (Char[], Int32[]) = IList
    (Int32[], Char[]) = IList
    (IEnumerable`1, IEnumerable`1) = IEnumerable
    (String, Array) = Object
    (Array, String) = Object
    (Char[], Int32[]) = IList
    (Form, SplitContainer) = ContainerControl
    (SplitContainer, Form) = ContainerControl
    

    Появится тест List'1 IList, потому что я протестировал typeof(List<int>) с помощью typeof(List<String>); и Dictionary'2 являются Dictionary<String, String>. Извините, что я не выполнял работу, чтобы представить точные имена типов.

Ответ 2

Простейшим случаем будет итерация по базовым типам одного объекта и проверка их на назначение другим типом, например:

  • код

    public Type GetClosestType(Type a, Type b) {
        var t=a;
    
        while(a!=null) {
            if(a.IsAssignableFrom(b))
                return a;
    
            a=a.BaseType;
        }
    
        return null;
    }
    

Это создаст System.Object для двух типов, которые не связаны между собой, если они оба класса. Я не уверен, соответствует ли это поведение вашим требованиям.

Для более сложных случаев я использую собственный метод расширения, называемый IsExtendablyAssignableFrom.

Он может обрабатывать различные числовые типы, генерики, интерфейсы, общие параметры, неявные преобразования, обнулять, боксировать/распаковывать и почти все типы, с которыми я сталкивался при реализации моего собственного компилятора.

Я загрузил код в отдельный репозиторий github [здесь], чтобы вы могли использовать его в своем проекте.

Ответ 3

Если вы посмотрите только на базовые классы, проблема тривиальна, и решение дается ответом Impworks ( "итерация над родителями одного объекта и проверка их на назначение другим типом" ).

Но если вы хотите включить и интерфейсы, нет единственного решения проблемы, поскольку вы отмечаете себя с примером IDelta и ICharlie. Два или более интерфейса могут быть в равной степени "хорошими", поэтому нет единственного лучшего решения. Можно легко построить произвольно сложные диаграммы (графики) наследования интерфейсов, и на таких диаграммах легко видеть, что там нет четко определенного "FindAssignableWith".

Кроме того, ковариация/контравариантность в С# используются для типов дисперсии типов generic. Позвольте мне привести пример. Допустим, что

type1: System.Func<string>
type2: System.Func<Tuple<int>>

тогда, конечно, с базовыми классами, "FindAssignableWith" может быть

solutionA: System.MulticastDelegate

Но тип Func<out T> также является ковариантным (out) в его параметре типа T. Поэтому тип

solutionB: System.Func<System.Object>

также является решением в том смысле, что оно IsAssignableFrom двух заданных типов type1 и type2. Но то же самое можно сказать и о

solutionC: System.Func<System.IComparable>

который работает, потому что string и Tuple<> являются IComparable.

Таким образом, в общем случае нет единственного решения. Поэтому, если вы не укажете точные правила, описывающие то, что вы хотите, мы не сможем найти алгоритм, который найдет ваше решение.