How to constrain generic type to a superclass instead a subclass of another type?

In Java 5 you can use the following syntax for variance in generic types:

public class A {...}
public class AA extends A {...}

public class MyContainer<E>
{
void setComparer(Comparator< super E> c) {...}
...
}

This means that you can pass instances of either Comparer<A> or Comparer<AA> to MyContainer<AA>.setComparer.

Is there a way to specify this type of constraint in .NET The where clause can be used to restrict the type the OTHER way ("where type inherits from another type") but can I define "where type is base class of another type" In other words I need something like the following:

public class MyContainer<T>
{
void SetComparer<U>(IComparable<U> c) where U : super T {...}
...
}

Thank you for any ideas (but flames about my stupidity are not welcome ;););) ).

Milan


Answer this question

How to constrain generic type to a superclass instead a subclass of another type?

  • Jazz1993

    Given just the description you provided I'd say no but the more I think about it the more I think what you want doesn't make sense.  A superclass (in your terms) would be a derived class.  If it is a derived class then their is no way to know at compilation time the methods available to the derived class.  Therefore your interface would be limited to the base class members.  Thus your constraint would simply be the base class.

    Given your sample of 2 classes A and AA (where AA derives from A) I assume that you implement IComparable and IComparable<> in each class or even in a separate class.  Here is my sample code equating this:

    class A { ... }
    class AA : A { ... }

    class ComparatorA : IComparable, IComparable<A> { ... }
    class ComparatorAA : ComparatorA, IComparable<AA> { ... }

    Notice that I used a separate class for the comparators here as is typical but A and AA could have just as easily implemented the interfaces themselves.  The important thing however is that ComparatorAA derives from ComparatorA.  Therefore ComparatorAA actually implements 3 interface: IComparable, IComparable<A> and IComparable<AA>.  That is the key for working with derived classes.  Since A and AA both use a comparator that derives from the same class you can now define your generic class as follows:

    class Container<T>
    {
        void SetComparator ( IComparable<T> comparator ) { ... }
    }

    Note that you could also use IComparable here and it would still work correctly.  However by explicitly specifying the type you are limiting it to only those that support comparison against the container contents.   The following code can then be used:

    Container<A> myContainer = new Container<A>();
    myContainer.SetComparator(new ComparatorA()); or
    myContainer.SetComparator(new ComparatorAA());

    Note however that this wouldn't work:

    Container<AA> myContainer = new Container<AA>();
    myContainer.SetComparator(new ComparatorA());

    And this shouldn't work because you are assuming that the base class is sufficiently informed enough to be able to compare objects that might differ in the derived class fields.  That is why virtual functions work the way they do.  Only the derived class is knowledgeable enough to determine if two of its instances are equivalent (or whatever).

    Hope this helps,
    Michael Taylor - 10/8/05


  • ubm0158

    I'm not convinced this additional functionality is really that useful.  I posted a full treatment of my thoughts on my blog here: http://www.srtsolutions.com/public/item/109651

    Short excerpts:  "The book example from the above link is interesting, but I can easily provide two possible modifications within the C# language that work just as well, or even better."

    And, my opinion of the Book example posted above:

    "I’m not sure the original NamedObjectComparer has real meaning (or at least that it’s really what the author intended). The NamedObjectComparer can be used to compare any two objects that have names. Do you really want to compare a Book to an Employee Or a Street to a Customer That’s valid with this object, but I doubt that have a proper semantic meaning.

    Of course, if you really did intend to compare different types that both have names, simply change the declaration of the collection:

    new SortedCollection<INamedObject>(new NamedObjectComparer());"

    Comments weclome.



  • Francois Malgreve

    The Short answer is "no". This is something you can do in Java but not in C#. It would be occasionally useful, but we currently have not come up with a good way to do it, given the constraints we have.

    Note that in a previous life I was the guy who came up with wildcards for Java, and ran the group who implemented them. So it is not that I don't think about it! :-)

    Mads

  • KamalHWZ

    Let me try to provide another example to illustrate the point.

    Suppose the following interface


    interface INamedObject { string Name {get;} }
     

    and the following IComparer class


    class NamedObjectComparer : IComparer
    {
        public int Compare(T a, T b)
        {
            return String.Compare(a.Name, b.Name);
        }
    }

     


    This comparer class is good for comparing instances of any class that implement INamedObject, so if we have a sorted collection class as follows,


    class SortedCollection : ICollection
    {
        public SortedCollection(IComparer) {...}
    }

     


    then of course we can create a collection of INamedObjects that are enumerated in alphabetical order, thus


    new SortedCollection(new NamedObjectComparer());
     


    However, consider a concrete implementation of INamedObject,


    class Book : INamedObject
    {
        private string title;
        ...
        public string Name {get {return title;} }
    }

     


    The NamedObjectComparer class could reasonably be used to sort Books alphabetically. It doesn't require any specific knowledge of the Book class other than its name. Unfortunately, we can't create a sorted collection of Books using SortedCollection's constructor


    new SortedCollection<Book>(new NamedObjectComparer()); //doesn't compile
     


    because NamedObjectComparer implements IComparer<INamedObject>, not IComparer<Book>. The point is that if you define a generic comparer class, you shouldn't have to sub-class it for each actual type you want to compare. That's supposed to be one of the key advantages of generic code.

    Anyway, I guess the upshot is that there doesn't seem to be an easy way to define the constructor of SortedCollection<T> such that is will accept a comparer of anything other than T. You could define multiple type parameters on the class declaration itself


    class SortedCollection<S, T> : ICollection<T> where T : S
     


    but this solution doesn't scale well - for each method of SortedCollection where this problem occurs, an additional type parameter would have to be introduced.

  • How to constrain generic type to a superclass instead a subclass of another type?