Search code examples
c#listgenericsadditioncovariance

Generic Bases, Covarience


I have a generic base class from which other generic classes inherit:

public class B<T> {

    private List<T> parent;

    public bool IsInParent() { return parent.Contains(this); }

    public void Attach() { parent.Add(this); }

}

The two this give the error "cannot convert from B<T> to T. I understand that this is due to the fact that this could be something other that T (covariance) and therefore won't go into a List<T>. That much I understand.

If I state that T inherits from B<T> there is no error for the first this but the error for the Add this remains.

public class B<T> where T : B<T> { ..... ]

Now surely if every T inherits from B<T>, then every B<T> is a T and therefore should go into a List<T>.

What am I doing wrong?


Solution

  • The two this give the error "cannot convert from B<T> to T. I understand that this is due to the fact that this could be something other that T (covariance) and therefore won't go into a List<T>.

    I don't think you do understand it.

    First: this is of type B<T>. Imagine instead of the abstract B<T> we made it more concrete:

    class Comparer<T> // I can compare two things of type T.
    {
        ....
    

    this inside Comparer<T> will be of type Comparer<T>. If you have a List<T>, you can put a T into the list. Suppose T is Apple. this can compare two apples. The List<T> can contain two apples. But the List<T> cannot contain two comparers of apples because that's not its type!

    Second, you don't understand what the word "covariance" means. I don't know what you think it means but my guess is that you believe that "covariance" means "assignment compatibility". That's usually what people believe "covariance" means when they are wrong about its meaning.

    "Assignment compatibility" is the property that a value of type Apple can go into a variable of type Fruit, because an Apple is assignment compatible with a storage for Fruit. That is not covariance.

    Covariance is the property that an assignment compatibility relationship is preserved by a generic type.

    That is: an Apple can go into a variable of type Fruit, and therefore an IEnumerable<Apple> can go into a variable of type IEnumerable<Fruit>. The relationship of assignment compatibility is preserved as the types are varied by making them generic, and therefore IEnumerable<T> is co-variant; the things vary together in the same direction.

    If I state that T inherits from B<T> there is no error for the first this but the error for the Add this remains.

    First off: do not do this. This is a bad pattern. It is a variation on the C++ pattern called the "curiously recurring template pattern". I have seen it used many, many times in C#, and almost always used wrong. Avoid it. It makes your types complicated, hard to use, hard to understand, and it makes you think that you have a constraint on your type that C# does not support.

    Now surely if every T inherits from B<T>, then every B<T> is a T and therefore should go into a List<T>.

    And now you perhaps begin to understand why it is that this pattern is so awful. It makes you believe completely crazy false things because it is so confusing!

    Let's again make your sentence less confusing. We'll replace T with Apple and B<T> with Fruit and we have:

    Now surely if every apple is a kind of fruit, then every fruit is an apple and therefore should go into a bowl of apples".

    Your conclusion is that apples are fruits, and therefore all fruits are apples, and so you can put a banana into a bowl of apples and it is still a bowl of apples. Plainly this is nonsensical.

    What am I doing wrong?

    You're building generic types that are so complicated that you can't understand them correctly. That means that the people using your code will not be able to understand them either. Find a simpler way to solve your problem.