Как найти наименьший присваиваемый тип в двух типах (дубликат)?
Вот два метода расширения для использования
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
не.
Ответы
Ответ 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...