13

Here're two extension methods for use

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 returns null if they don't have common implemented interface.
  • FindBaseClassWith returns System.Object if they have no more derivative common base class.
  • FindBaseClassWith returns null if one of parameters was an interface.
  • Both they return null if any of parameter was null.

And the signature of method in finally solution would be like:

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

Reflection and Linq are restricted to use, except there are no other way.

Are there good ways to find the best fit of common type between type1 and type2?

Or are there something better to achieve this?


update:

By my personal understanding, because of the ability to implement multiple interfaces with a class, the FindInterfaceWith could possibly need to call FindBaseClassWith internally; otherwise the best choice of type would be undecidable.

If this supposition was correct, then the FindInterfaceWith becomes a redundant method; because of the only difference between FindInterfaceWith and FindAssignableWith is:

FindInterfaceWith returns null if there was a best choice of class; while FindAssignableWith returns the exact class directly.

Otherwise, they both return a best choice of interface.

This is about saying the original assumption was irrational. That is, FindInterfaceWith cannot be implemented if FindAssignableWith is not.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • 2
    [easiest-way-to-get-a-common-base-class-from-a-collection-of-types](http://stackoverflow.com/a/353533/649524) – Tilak Jan 01 '13 at 03:56
  • That can be done by `FindBaseClassWith`. – Ken Kin Jan 01 '13 at 04:24
  • I may misunderstand the problem, but wouldn't that be as simple as using reflection to navigate up the list of parent types (using the Type.BaseType property), and matching the base types that match between the two types? – Brian Mains Jan 01 '13 at 04:02

4 Answers4

10

Here is my implementation:

FindAssignableWith, FindBaseClassWith and FindInterfaceWith implementations

// 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);
}

Remark regarding FindInterfaceWith implementation

Any interfaces that implements either IEnumerable or IEnumerable<T> will be selected before others, what I considered not to be correct

Open ended question of FindInterfaceWith

allow multiple interfaces to be implemented in one class, in this case first one of interfaces will be returned by FindInterfaceWith, because there is no way how to know which of interfaces IA or IB are preferable in general in following sample

multiple_interfaces_implementing

Interfaces and classes hierarchy

    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 {}

Test cases

And set of test cases using NUnit assertions:

FindBaseClassWith assertions example

// 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 assertions example

// 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 assertions example

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

Discussion at CodeReview

Review of this answer at codereview.stackexchange.com

ps:
Full sources available [here]

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
Akim
  • 8,469
  • 2
  • 31
  • 49
  • `FindEqualTypeWith` has been added. Could you provide more samples of class hierarchy that you are going to work with? – Akim Jan 03 '13 at 14:54
  • It will be great if you could provide some inputs as assertions – Akim Jan 03 '13 at 15:12
  • I think that your implementation of `FindInterfaceWith` might result misleading of you. – Ken Kin Jan 03 '13 at 16:13
  • You are right. I have updated `FindInterfaceWith`: if parameter is interface take it also into account: `IConvertible` vs. `string` is `IConvertible` and `ICollection` vs. `Array` is `ICollection` – Akim Jan 03 '13 at 16:43
  • Take a look at MSDN articles about [System.String](http://msdn.microsoft.com/en-us/library/system.string.aspx) and [System.Array](http://msdn.microsoft.com/en-us/library/system.array.aspx) — both classes implement interfaces `ICloneable` and `IEnumerable`, and this two are independent, so both answers are correct – Akim Jan 03 '13 at 18:04
  • 2
    sorry, my personal telepathist is on winter vacation now, thus provide exact definition of `most suitable`. Until there is a possibility to design class that implements multiple interfaces, you could not resolve this issue. My proposal, `FindEqualTypeWith ` should returns several best types, i.e. enumeration of types instead of one type. See open question to `FindInterfaceWith` at original answer. – Akim Jan 03 '13 at 18:27
  • There is a concept of `copy constructor` which define how to create instance of one type out of instance of another type. For `sysytem.string` there are: `String..ctor (Char* value)`, `String..ctor (SByte* value)` and `String..ctor (Char[] value)`. Last one could be to be helpful in case of `FindEqualTypeWith`, but `char[]` could be also `IClonable` and `IEnumerable`. Thus all interfaces are equal, and there is only artifactual exception for `IEnumerable`. – Akim Jan 04 '13 at 19:08
  • See answer update about multiple interface implementation: this is some kind of japanis logic puzzle, until you clearly state why one interfaces are "better" then anoter. In `string` vs. `Array` example you could keep references to entities either as `IClonable` or `IEnumerable`, but you can't convert `Array` to `string` or otherwise beause there is no conversion defined – Akim Jan 04 '13 at 19:33
  • >> For example of `String`, before it going to invoke the constructor There is no constructor for `system.string` like `new string(IEnumerable)`, nearest suitable is `new string(char[])` – Akim Jan 04 '13 at 19:38
  • Unfortunaly, `char[]` has base class of `System.Array` and is implementing both `ICloneable` and `IEnumerable`! – Akim Jan 04 '13 at 20:00
  • I have updated my `FindInterfaceWith` implementation, see remark – Akim Jan 04 '13 at 21:21
  • Unless there is possibility to have a class implements multiple interfaces, you have to admit that there could be more than one type to fit both of target types. **If you have some additional criterias from you domain model** (i.e. some interfaces are more general then others) **you could apply some kind of strategy to algorithm**. Unfortunaly, one criteria that you had provided is not clearly specified at all. That all – Akim Jan 04 '13 at 22:32
  • 1
    I have sent this answer to [review at Code Review forum](http://codereview.stackexchange.com/q/20369/14870). May be someone could improve it – Akim Jan 10 '13 at 06:31
  • I renamed all `FindEqualTypeWith` to `FindAssignableWith`; but I'm not able to revise the external source code; thank you for all the work again :) – Ken Kin Dec 15 '17 at 17:30
2

Oh, yay, I get to show off something I recently wrote for something else! :)

Caveat: This code is not the most efficient in the world, and it's VERY poorly commented - it was for a personal project, and I already knew how it worked - but I think it'll get you what you're after...

The method you'd be most interested in would be public static Tuple<Type, IEnumerable<Type>> GetCommonBases(Type left, Type right)

The Tuple returned is <common base class, (list of common interfaces)>

A quick summary: this class, when given a type, does the following:

  • Reverse walks up the given type until it hits no more base types, pushing each into a 'working stack'

  • Pops each base type off the working stack, inserting it into a tree-like structure; If the type implements any interfaces, it adds nodes for those interface types as well

  • the helper method GetCommonBases creates one of these TypeTree structures for the first type, then 'merges' in the type tree for the other given type: it does so by walking down common base types until it finds a point where you have a common base type between the two types, at which point two branches of the tree are formed. It then "drills down" to each type from the root (i.e., System.Object), then finds the first point of deviation. The parent of this point of deviation is the Common base type.

  • The interfaces part relies on the definition of Interfaces, which "inherits" any Interface nodes for any ancestor. The GetCommonBases method pulls a list of any interfaces implemented by the two passed in types and returns an intersection of these two lists - that is, a set of interfaces that both passed in types implement.

  • the method then returns these two bits of information as a Tuple<Type, IEnumerable<Type>>, where the first item is the common base type, if any, and the second item is the intersection of common interfaces


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

}

JerKimball
  • 16,584
  • 3
  • 43
  • 55
  • 1
    I can add a description of what the class does, but other than that, the code is free for anyone's use; I just found it useful for a side project I was working on. – JerKimball Jan 03 '13 at 05:40
1

I'll have a default implementation and some well-known classes and interface sorted by priority to have in mind. Here my implementation:

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;

}

Hope it helps.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
Ivo
  • 8,172
  • 5
  • 27
  • 42
1

update +1: And now, without the stupid mistake and some more details

I suppose this is what you are looking for:

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

Basically it treats the base classes and interfaces the same in the ordering.
I suppose the base implementation is from [here].
What I did is basically glue the two methods together, without changing the semantics of the original functionality.

Example:

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...
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
atlaste
  • 30,418
  • 3
  • 57
  • 87