Search code examples
c#genericscollectionscovariancetype-constraints

C# collections type constrained generics


I am trying to do something in C# that is pretty straightforward in Java, using wildcard type-bounding. I've tried to boil it down to only the Java code necessary to illustrate the problem (compiles):

public abstract class ParentClass {
    public abstract SomeBindingList<? extends ParentClass> parentList();
}

public class ChildClass extends ParentClass {
    private SomeBindingList<ChildClass> myList;

    public ChildClass() {
        // this could load from/bind to a database
        myList = new SomeBindingList<ChildClass>();
    }

    public SomeBindingList<? extends ParentClass> parentList() {
        return myList;
    }
}

Perhaps I need to emphasize the following line since someone marked this as a duplicate: SomeBindingList is a 3rd-party BindingList so I can't change it. It is parameterized and can't be replaced with a non-parameterized version.

The problem, of course, is how to implement the parentList() method in the ChildClass to return a list that can be used as a list of ParentClass objects.

It seems like there should be some way to use the where keyword to provide a constrained type in C#, but I can't make it work (syntactically at least) with a class that already extends another class, i.e. ChildClass, and there doesn't seem to be a way to parameterize just the return value of a method (or a property).

I could make a new list and put all the ChildClass items in the new list as ParentClass items, but (aside from being kludgy) I was afraid that would interfere with the behavior of SomeBindingList.

I'm no C# expert, so I'm sure someone more familiar with the language knows the answer. Thanks!

@CodeCaster -- I've tried many variations of C# code (this does not compile, and I can't find a variation that does):

public abstract class ParentClass {
    public abstract List<T> parentList<T>() where T : ParentClass;
}

public class ChildClass {
    public List<ChildClass> myList;

    public ChildClass() {
        myList = new List<ChildClass>();
    }

    public override List<T> parentList<T>() where T : ParentClass {
        return myList;
    }
}

I've tried parameterizing ChildClass<T> but that just causes generation of a List<ChildClass<T>> which is not a List<T>


Solution

  • I think your issue here is related to the desired co-variance of the relationship between the inheritance hierarchy of the generic 3rd-party SomeBindingList<T> class and the hierarchy of the types used as parameters.

    First, let me give you code that will compile:

    public interface ISomeBindingList<out T>
    {
    }
    
    public abstract class ParentClass
    {
        public abstract ISomeBindingList<ParentClass> parentList();
    }
    
    public class ChildClass : ParentClass
    {
        private ISomeBindingList<ChildClass> myList;
    
        public ChildClass()
        {
            // this could load from/bind to a database
            // myList = new SomeBindingList<ChildClass>(); // <-- we need to figure out this
        }
    
        public override ISomeBindingList<ParentClass> parentList()
        {
            return myList;
        }
    }
    

    C# does not provide generic type co-variance for classes. But it does for interfaces. You will have to think outside the box and implement a trivial adapter for your 3rd-party SomeBindingList<T>, which implements only the lifted members that are co-variantly compatible, that is: those members where T only occurs as output.

    For example, assuming SomeBindingList<T> contains a method T Get(), you would lift this member to an adapter interface, and create a trivial adapter implementation.

    This would be the complete code:

    public interface ISomeBindingList<out T>
    {
        T Get();
    }
    
    public class SomeBindingListAdapter<T> : SomeBindingList<T>, ISomeBindingList<T>
    {
    }
    
    public abstract class ParentClass
    {
        public abstract ISomeBindingList<ParentClass> parentList();
    }
    
    public class ChildClass : ParentClass
    {
        private ISomeBindingList<ChildClass> myList;
    
        public ChildClass()
        {
            // this could load from/bind to a database
            myList = new SomeBindingListAdapter<ChildClass>();
        }
    
        public override ISomeBindingList<ParentClass> parentList()
        {
            return myList;
        }
    }