如何找到两种类型中最小的可分配类型(重复)?

时间:2013-01-01 03:40:51

标签: c# reflection types

这是两种使用的扩展方法

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仅限使用,除非没有别的办法。

是否有很好的方法可以在type1type2之间找到最适合的常见类型?

还是有更好的方法来实现这一目标吗?


更新:

根据我个人的理解,由于 使用类实现多个接口 的能力,FindInterfaceWith可能需要调用FindBaseClassWith内部;否则最好的选择类型将是不可判定的。

如果这个假设是正确的,那么FindInterfaceWith就成了一个冗余的方法;因为FindInterfaceWithFindAssignableWith之间的唯一区别是:

如果有最佳的课程选择,

FindInterfaceWith会返回null;而FindAssignableWith会直接返回确切的类。

否则,它们都会返回最佳的界面选择。

这是关于说最初的假设是不合理的。也就是说,如果FindInterfaceWith不是 ,则 FindAssignableWith无法实施。

4 个答案:

答案 0 :(得分:10)

这是我的实施:

FindAssignableWithFindBaseClassWithFindInterfaceWith实施

// 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's 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实施

的备注

任何实现IEnumerableIEnumerable<T>的接口都将在其他接口之前被选中,我认为不正确

FindInterfaceWith

的开放式问题

允许在一个类中实现多个接口,在这种情况下,第一个接口将由FindInterfaceWith返回,因为无法知道哪个接口{在以下示例

中,{1}}或IA通常更受欢迎

multiple_interfaces_implementing

接口和类层次结构

IB

测试用例

使用 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

在CodeReview讨论

Review of this answer at codereview.stackexchange.com

ps
完整资源[here]

答案 1 :(得分:2)

哦,是的,我可以炫耀一下我最近写的东西! :)

警告:这段代码不是世界上效率最高的,而且评论非常糟糕 - 这是个人项目,我已经知道它是如何工作的 - 但我认为它会让你得到你所追求的...

您最感兴趣的方法是 public static Tuple<Type, IEnumerable<Type>> GetCommonBases(Type left, Type right)

返回的元组是&lt;公共基类,(公共接口列表)&gt;

快速摘要:给定类型时,此类执行以下操作:

  • 反向走动给定类型,直到它不再触及基础类型,将每个类型推入“工作堆栈”

  • 将每个基本类型从工作堆栈中弹出,将其插入树状结构中;如果类型实现任何接口,它还为这些接口类型添加节点

  • 辅助方法GetCommonBases为第一种类型创建这些TypeTree结构之一,然后在另一种给定类型的类型树中“合并”:它通过向下走基类型,直到它找到一个点,在这两个类型之间有一个公共基类型,此时形成树的两个分支。然后它从根部“向下钻取”到每种类型(即System.Object),然后找到第一个偏差点。此偏差点的父级是Common基类型。

  • 接口部分依赖于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;
   }

}

答案 2 :(得分:1)

我将有一个默认实现和一些众所周知的类和接口按优先级排序。在这里我的实施:

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's 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's 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;

}

希望它有所帮助。

答案 3 :(得分:1)

更新+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();
}

基本上它在排序中将基类和接口视为相同。
我想基础实现来自[here]。
我所做的基本上是将两种方法粘合在一起,而不改变原始功能的语义。

示例:

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...