Jephron
Jephron

Reputation: 2748

C# generics: list of interface-implementing objects

I'm trying to understand some specifics about C#'s generics.

If I have a method defined as such

 public static void AssertContains<T>(IEquatable<T> val, List<IEquatable<T>> optionsObjs, XML xml, string context)

and a class that implements IEquatable,

public class Tag : IEquatable<Tag>
{
    public string id;
    public bool Equals(Tag other)
    {
        return other.id == this.id;
    }
}

why is the following invalid?

AssertContains(aTag, aListOfTags, el, "");

Upvotes: 0

Views: 142

Answers (5)

Eric Lippert
Eric Lippert

Reputation: 660159

This question is asked in some form almost every day.

A list of animals may not be used in any context in which a list of giraffes is needed. Why? Because a list of animals might contain a tiger.

A list of giraffes may not be used in any context in which a list of animals is needed. Why? Because a list of animals might have a tiger inserted into it, and now you've inserted a tiger into what is actually a list of giraffes.

However, a sequence -- IEnumerable<T> -- of giraffes may be used as a sequence of animals. Why is this legal, when it is illegal to do so with lists? Because sequences have no method that allows you to add a tiger to a sequence of animals.

Do a search on this site or the internet for "covariance and contravariance in C#" and you will find lots of information on this topic.

Upvotes: 2

Nikolay Tsonev
Nikolay Tsonev

Reputation: 9

I think the problem is in List<IEquatable<T>> because it is generic in generic. While using the below statement you avoid that generic-in-generic problem.

public static void AssertContains<T>(T val, List<T> optionsObjs, XML xml, string context) where T: IEquatable<T>

In order to demonstrate you that below I wrote it using 2 generic types:

public static void AssertContains<T, Z>(IEquatable<T> val, List<Z> optionsObjs, XML xml, string context) where Z : IEquatable<T>

That works out the same way, but taking a careful look leads us to the above statement, because IEquatable<T> val may be replaced by Z val. So the 2 generic types are useful if you had a generic parameter:

public static void AssertContains<T, Z>(T anotherValue, IEquatable<T> val, List<Z> optionsObjs, XML xml, string context) where Z : IEquatable<T>

Upvotes: -1

Arturo Menchaca
Arturo Menchaca

Reputation: 15982

This happens because List<T> isn't covariant, so you cant convert List<Type> to List<BaseType>.

In your case List<Tag> and List<IEquatable<Tag>>.

You can fix this changing optionsObjs parameter type to IEnumerable<IEquatable<T>>:

public static void AssertContains<T>(IEquatable<T> val, 
                                     IEnumerable<IEquatable<T>> optionsObjs,
                                     XML xml,
                                     string context)

Thats because IEnumerable<T> is covariant, so you can do things like this:

IEnumerable<IEquatable<Tag>> list = new List<Tag>(); 

Upvotes: 2

Mark Brackett
Mark Brackett

Reputation: 85665

That doesn't work because of contravariance; basically, a List<Derived> cannot be treated as a List<Base>.

Upvotes: 1

Jephron
Jephron

Reputation: 2748

I figured out that defining the method as

public static void AssertContains<T>(T val, List<T> optionsObjs, XML xml, string context) where T: IEquatable<T>

makes AssertContains(aTag, aListOfTags, el, ""); valid.

However, if someone can explain why my original post was wrong, I'll accept their answer.

Upvotes: 0

Related Questions