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

Как найти наименьший присваиваемый тип в двух типах (дубликат)?

Вот два метода расширения для использования

public static Type FindInterfaceWith(this Type type1, Type type2) {
    // returns most suitable common implemented interface
}

public static Type FindBaseClassWith(this Type type1, Type type2) {
    // returns most derivative of common base class
}
  • FindInterfaceWith возвращает null, если у них нет общего реализованного интерфейса.
  • FindBaseClassWith возвращает System.Object, если у них нет более производного общего базового класса.
  • FindBaseClassWith возвращает null, если один из параметров был интерфейсом.
  • Оба они возвращают null, если любой из параметров был null.

И подпись метода в окончательном решении будет выглядеть так:

public static Type FindAssignableWith(this Type type1, Type type2) {
    // what should be here?
}

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

Есть ли хорошие способы найти наилучшее соответствие общего типа между type1 и type2?

Или есть что-то лучше для этого?


обновление:

По моему личному пониманию, из-за возможности реализовать несколько интерфейсов с классом, FindInterfaceWith возможно, потребуется вызвать FindBaseClassWith внутренне; в противном случае лучший выбор типа был бы неразрешимым.

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

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

В противном случае оба они возвращают лучший выбор интерфейса.

Речь идет о том, что исходное предположение было иррациональным. То есть FindInterfaceWith не может быть реализовано, если FindAssignableWith не.

4b9b3361

Ответ 1

Вот моя реализация:

FindAssignableWith, FindBaseClassWith и FindInterfaceWith реализации

// provide common base class or implemented interface
public static Type FindAssignableWith(this Type typeLeft, Type typeRight)
{
    if(typeLeft == null || typeRight == null) return null;

    var commonBaseClass = typeLeft.FindBaseClassWith(typeRight) ?? typeof(object);

    return commonBaseClass.Equals(typeof(object))
            ? typeLeft.FindInterfaceWith(typeRight)
            : commonBaseClass;
}

// searching for common base class (either concrete or abstract)
public static Type FindBaseClassWith(this Type typeLeft, Type typeRight)
{
    if(typeLeft == null || typeRight == null) return null;

    return typeLeft
            .GetClassHierarchy()
            .Intersect(typeRight.GetClassHierarchy())
            .FirstOrDefault(type => !type.IsInterface);
}

// searching for common implemented interface
// it possible for one class to implement multiple interfaces, 
// in this case return first common based interface
public static Type FindInterfaceWith(this Type typeLeft, Type typeRight)
{
    if(typeLeft == null || typeRight == null) return null;

    return typeLeft
            .GetInterfaceHierarchy()
            .Intersect(typeRight.GetInterfaceHierarchy())
            .FirstOrDefault();   
}

// iterate on interface hierarhy
public static IEnumerable<Type> GetInterfaceHierarchy(this Type type)
{
    if(type.IsInterface) return new [] { type }.AsEnumerable();

    return type
            .GetInterfaces()
            .OrderByDescending(current => current.GetInterfaces().Count())
            .AsEnumerable();
}

// interate on class hierarhy
public static IEnumerable<Type> GetClassHierarchy(this Type type)
{
    if(type == null) yield break;

    Type typeInHierarchy = type;

    do
    {
        yield return typeInHierarchy;
        typeInHierarchy = typeInHierarchy.BaseType;
    }
    while(typeInHierarchy != null && !typeInHierarchy.IsInterface);
}

Замечание относительно реализации FindInterfaceWith

Любые интерфейсы, которые реализуют либо IEnumerable, либо IEnumerable<T>, будут выбраны перед другими, что Я считаю не правильным

Открытый вопрос FindInterfaceWith

позволить реализовать несколько интерфейсов в одном классе, в этом случае сначала один из интерфейсов будет возвращен FindInterfaceWith, потому что нет способа узнать, какой из интерфейсов IA или IB предпочтительнее вообще в следующем примере

multiple_interfaces_implementing

Иерархия интерфейсов и классов

    public interface IBase {}
    public interface ISomething {}
    public interface IDerivied: IBase {}
    public interface IDeriviedRight: IDerivied {}
    public interface IDeriviedLeft: IDerivied, IDisposable {}

    public class AnotherDisposable: IDisposable {
        public void Dispose() {
        }
    }

    public class DeriviedLeft: IDeriviedLeft {
        public void Dispose() {
        }
    }

    public class SubDeriviedLeft: DeriviedLeft {}
    public class SecondSubDeriviedLeft: DeriviedLeft {}
    public class ThirdSubDeriviedLeft: DeriviedLeft, ISomething {}

    public class Another {}
    public class DeriviedRight: IDeriviedRight {}

Контрольные случаи

И набор тестовых примеров с использованием NUnit утверждений:

FindBaseClassWith пример утверждений

// FindBaseClassWith returns null if one of parameters was an interface. 
// FindBaseClassWith  return null if any of parameter was null. 
Assert.That(typeof(DeriviedLeft).FindBaseClassWith(typeof(DeriviedLeft)), Is.EqualTo(typeof(DeriviedLeft)));

FindInterfaceWith пример утверждений

// FindInterfaceWith returns null if they don't have common implemented interface. 
// FindBaseClassWith  return null if any of parameter was null. 
Assert.That(typeof(DeriviedLeft).FindInterfaceWith(typeof(DeriviedLeft)), Is.EqualTo(typeof(IDeriviedLeft)));

FinAssignableWith пример утверждений

Assert.That(typeof(DeriviedLeft).FindAssignableWith(typeof(DeriviedLeft)), Is.SameAs(typeof(DeriviedLeft)));

Обсуждение в CodeReview

Отзыв об этом ответе на codereview.stackexchange.com

ps:
Доступны полные источники [здесь]

Ответ 2

О, да, я могу показать что-то, что я недавно написал для чего-то еще!:)

Предостережение: этот код не самый эффективный в мире, и он ОЧЕНЬ плохо комментировал - это было для личного проекта, и я уже знал, как он работает, но я думаю, что это даст вам то, что вы после...

Метод, который вам больше всего интересен, будет public static Tuple<Type, IEnumerable<Type>> GetCommonBases(Type left, Type right)

Возвращенный Tuple - это стандартный базовый класс (список общих интерфейсов) >

Краткое резюме: этот класс при задании типа выполняет следующие действия:

  • Обратный перемещается по заданному типу до тех пор, пока он не станет больше базовых типов, нажимая каждый в "рабочий стек"

  • Перетаскивает каждый базовый тип из рабочего стека, вставляя его в древовидную структуру; Если тип реализует любые интерфейсы, он также добавляет узлы для этих типов интерфейсов.

  • вспомогательный метод GetCommonBases создает одну из этих структур TypeTree для первого типа, затем "объединяется" в дереве типов для другого заданного типа: он делает это, спустив общие базовые типы, пока не будет находит точку, в которой у вас есть общий базовый тип между двумя типами, с которого формируются две ветки дерева. Затем он "свертывается" для каждого типа из корня (т.е. System.Object), затем находит первую точку отклонения. Родитель этой точки отклонения является общим базовым типом.

  • Часть интерфейсов основана на определении Interfaces, которое "наследует" любые интерфейсные узлы для любого предка. Метод GetCommonBases вытаскивает список любых интерфейсов, реализованных двумя переданными типами, и возвращает пересечение этих двух списков, то есть набор интерфейсов, которые оба передаются в типах.

  • метод возвращает эти два бита информации как Tuple<Type, IEnumerable<Type>>, где первым элементом является общий базовый тип, если он есть, а второй элемент - это пересечение общих интерфейсов.


public class TypeTree
{
   private TypeTree()
   {
       Children = new List();
   }

   public TypeTree(Type value)
       : this()
   {
       // Get to the basest class
       var typeChain = GetTypeChain(value).ToList();
       Value = typeChain.First();
       foreach (var type in typeChain.Skip(1))
       {
           Add(type);
       }
   }

   public Type Value { get; private set; }
   public TypeTree Parent { get; private set; }
   public List Children { get; private set; }
   public IEnumerable Interfaces
   {
       get
       {
           var myInterfaces = Children.Where(c => c.Value.IsInterface);
           return Parent == null ? myInterfaces : myInterfaces.Concat(Parent.Interfaces).Distinct();
       }
   }

   public TypeTree Find(Type type)
   {
       if (Value == type)
           return this;
       return Children.Select(child => child.Find(type)).FirstOrDefault(found => found != null);
   }

   public TypeTree Add(Type type)
   {
       TypeTree retVal = null;
       if (type.IsInterface)
       {
           if (Value.GetInterfaces().Contains(type))
           {
               retVal = new TypeTree { Value = type, Parent = this };
               Children.Add(retVal);
               return retVal;
           }
       }
       var typeChain = GetTypeChain(type);
       var walkTypes =
           from baseType in typeChain
           let alreadyExists = Value == baseType || Children.Any(c => c.Value == baseType)
           where !alreadyExists
           select baseType;
       foreach (var baseType in walkTypes)
       {
           if (baseType.BaseType == Value)
           {
               // Add this as a child of the current tree
               retVal = new TypeTree { Value = baseType, Parent = this };
               Children.Add(retVal);
           }
           if (Value.IsAssignableFrom(baseType))
           {
               // we can add this as a child, potentially
               retVal = Children.Aggregate(retVal, (current, child) => child.Add(baseType) ?? current);
           }
           // add interfaces
           var interfaces = baseType.GetInterfaces().Where(i => i != type);
           foreach (var intType in interfaces)
           {
               (retVal ?? this).Add(intType);
           }
       }
       return retVal;
   }

   public override string ToString()
   {
       var childTypeNames = Children.Select(c => c.ToString()).Distinct();
       return string.Format("({0} {1})", Value.Name, string.Join(" ", childTypeNames));
   }

   public static Tuple> GetCommonBases(Type left, Type right)
   {
       var tree = new TypeTree(left);
       tree.Add(right);
       var findLeft = tree.Find(left);
       var findRight = tree.Find(right);

       var commonInterfaces =
           findLeft.Interfaces.Select(i => i.Value)
           .Intersect(findRight.Interfaces.Select(i => i.Value))
           .Distinct();

       var leftStack = new Stack();
       var temp = findLeft;
       while (temp != null)
       {
           leftStack.Push(temp);
           temp = temp.Parent;
       }
       var rightStack = new Stack();
       temp = findRight;
       while (temp != null)
       {
           rightStack.Push(temp);
           temp = temp.Parent;
       }
       var zippedPaths = leftStack.Zip(rightStack, Tuple.Create);
       var result = zippedPaths.TakeWhile(tup => tup.Item1.Value == tup.Item2.Value).Last();            
       return Tuple.Create(result.Item1.Value, commonInterfaces);
   }

   private static IEnumerable GetTypeChain(Type fromType)
   {
       var typeChain = new Stack();
       var temp = fromType;
       while (temp != null)
       {
           typeChain.Push(temp);
           temp = temp.BaseType;
       }
       return typeChain;
   }

}

Ответ 3

У меня будет реализация по умолчанию, а некоторые известные классы и интерфейс отсортированы по приоритету, чтобы иметь в виду. Здесь моя реализация:

private static List<Type> CommonTypesPriorities = new List<Type> 
                                       {
                                           typeof(IEnumerable), 
                                           typeof(Array), 
                                           typeof(IClonable)
                                       };

public static Type FindAssignableWith(this Type type1, Type type2)
{
    if(type1 == type2) 
        return type1;

    var baseClass = type1.FindBaseClassWith(type2);

    //if the base class is not object/null and it is not in the list, then return it.
    if(baseClass != typeof(object) && baseClass != null && !CommonTypesPriorities.Contains(type))
        return baseClass;

    var @interface = type1.FindInterfaceWith(type2);

    if(@interface == null)
        return baseClase;

    //if there no base class and the found interface is not in the list, return it
    if(baseClass != null && !CommonTypesPriorities.Contains(@interface)                         
        return @interface;

    //Now we have some class and interfaces from the list.

    Type type = null;
    int currentPriority;

    //if the base class is in the list, then use it as the first choice
    if(baseClass != null && CommonTypesPriorities.Contains(type))
    {
        type = baseClass;
        currentPriority = CommonTypesPriorities.IndexOf(type);
    }

    var interfaces1 = type1.GetInterfaces();
    var interfaces2 = type2.GetInterfaces();

    foreach(var i in interfaces1)
    {
        if(interfaces2.Contains(i))
        {
            //We found a common interface. Let check if it has more priority than the current one
            var priority = CommonTypesPriorities.IndexOf(i);
            if(i >= 0 && i < currentPriority)
            {
                currentPriority = priority;
                type = i;
            }
        }
    }

    return type;

}

Надеюсь, что это поможет.

Ответ 4

update +1: И теперь, без глупой ошибки и еще нескольких деталей

Я предполагаю, что это то, что вы ищете:

public static Type FindAssignableWith(this Type typeLeft, Type typeRight) {
    if(typeLeft==null||typeRight==null)
        return null;

    var typeLeftUion=typeLeft.GetInterfaceHierarchy().Union(typeLeft.GetClassHierarchy());
    var typeRightUion=typeRight.GetInterfaceHierarchy().Union(typeRight.GetClassHierarchy());

    return 
        typeLeftUion.Intersect(typeRightUion)
            .OrderByDescending(interfaceInHierarhy => interfaceInHierarhy.GetInterfaces().Contains(typeof(IEnumerable)))
            .ThenByDescending(interfaceInHierarhy => interfaceInHierarhy.Equals(typeof(IEnumerable)))
            .FirstOrDefault();
}

В основном он рассматривает базовые классы и интерфейсы одинаковые в упорядочении.
Я полагаю, что базовая реализация от [здесь].
То, что я сделал, в основном скрепляет два метода вместе, не изменяя семантику исходной функциональности.

Пример:

var result=typeof(char[]).FindAssignableWith2(typeof(string[]));
Console.WriteLine("{0}", typeof(char[]).FindAssignableWith2(typeof(string[]))); // IList
Console.WriteLine("{0}", typeof(Test).FindAssignableWith2(typeof(string[]))); // Object
// and so on...