Covariant generic parameter
Asked Answered
S

3

43

I'm trying to understand this but I didn't get any appropriate results from searching.

In C# 4, I can do

    public interface IFoo<out T>
    {

    }

How is this different from

    public interface IFoo<T>
    {

    }

All I know is the out makes the generic parameter covariant (??). Can someone explain the usage of <out T> part with an example? And also why is applicable only for interfaces and delegates and not for classes?

Sorry if it's a duplicate and close it as such if it is.

Stipule answered 28/6, 2011 at 14:59 Comment(2)
Read this: blogs.msdn.com/b/csharpfaq/archive/2010/02/16/…Kite
Archive link for the above comment: web.archive.org/web/20140709063137/http://blogs.msdn.com/b/…Incogitable
R
53

Can someone explain the usage of the out T part with an example?

Sure. IEnumerable<T> is covariant. That means you can do this:

static void FeedAll(IEnumerable<Animal> animals) 
{
    foreach(Animal animal in animals) animal.Feed();
}

...

 IEnumerable<Giraffe> giraffes = GetABunchOfGiraffes();
 FeedAll(giraffes);

"Covariant" means that the assignment compatibility relationship of the type argument is preserved in the generic type. Giraffe is assignment compatible with Animal, and therefore that relationship is preserved in the constructed types: IEnumerable<Giraffe> is assignment compatible with IEnumerable<Animal>.

Why is applicable only for interfaces and delegates and not for classes?

The problem with classes is that classes tend to have mutable fields. Let's take an example. Suppose we allowed this:

class C<out T>
{
    private T t;

OK, now think this question through carefully before you go on. Can C<T> have any method outside of the constructor that sets the field t to something other than its default?

Because it must be typesafe, C<T> can now have no methods that take a T as an argument; T can only be returned. So who sets t, and where do they get the value they set it from?

Covariant class types really only work if the class is immutable. And we don't have a good way to make immutable classes in C#.

I wish we did, but we have to live with the CLR type system that we were given. I hope in the future we can have better support for both immutable classes, and for covariant classes.

If this feature interests you, consider reading my long series on how we designed and implemented the feature. Start from the bottom:

https://blogs.msdn.microsoft.com/ericlippert/tag/covariance-and-contravariance/

Retardant answered 28/6, 2011 at 15:21 Comment(10)
Thanks Eric! That gives me the general idea and I'll read through your blog posts. PS: When I saw an answer from you, I was hoping for to see some hotel rooms and stolen keys but I will take Giraffes.Stipule
If C<T> has multiple fields whose types involve T, it could have methods which set t based upon information in another field. Further, even if fields could only be set in a constructor, it would be useful if something like a Tuple<Cat, ToyotaPrius> could be passed to code expecting a Tuple<Animal, Car>. I think a more a fundamental problem with covariant classes is that every different class produced form Foo<T> has its own set of static variables. If Foo<T>.Q() was an instance property that returned a static field, and one could have Foo<Animal> it = new Foo<Cat>();...Alleen
...which class' static field should be used to evaluate it.Q()? It would seem strange for method Q of a Foo<Animal> to actually bind to an instance method of class Foo<Cat>.Alleen
Nice answer, but link to blog is brokenPinery
@LarsMichael: Every ten years or so Microsoft changes blog systems and the links all get messed up. Thanks for the note, I'll fix it.Retardant
@EricLippert I have a question about your immutable classes in C# comment. Wouldn't making any properties strictly get, technically, be considered immutable? My thinking being methods are required to alter state so if none are provided the state cannot (without reflection, of course) be changed. Is that flawed? Also Oh the delicious irony of your comment and the condition of the link you provided. LOL To be fair, this was back in 2016, but, I found it funny and figured you might as well and we all need a boost these days, right? I hope you're well!Jarvey
@ErickBrown: Yes, if there was a way for the compiler and runtime to reliably know "everything in this class is set once and then only read" then we could reason that this immutable type could be safely covariant. The logic is sound; the remaining issue is therefore the engineering problem of designing a type system that tracks immutability along with everything else! That's the real work to do here. The recent C# 9 prototypes of "record types" is a possible step in this direction, so I look forward to learning more about that.Retardant
@EricLippert "Because it must be typesafe, C<T> can now have no methods that take a T as an argument" Why not, what BadThing is it preventing? Is it this: Cage<Animal> cage = new Cage<Mouse>(); cage.put(new Elephant());? Also, "Covariant class types really only work if the class is immutable" -- what does "immutable" mean in this context? A producer of Mice should be able to internally add a Mouse (or more derived) without compromising type safety. So does "immutable" then mean "not externally mutable"?Lioness
@BorisB.: Re your first question: yes, that is exactly the bad thing which we wish to prevent.Retardant
@BorisB.: Re your second question: if the type system could reason about "internal" vs "external" modifications to a field then yes, the type system could make certain kinds of covariance legal that are now illegal. But even facts like the "read-only-ness" of a field are not considered when doing the type analysis; in theory, they could be.Retardant
P
8

If we're talking about generic variance:

Covariance is all about values being returned from an operation back to the caller.

Contravariance It’s opposite and it's about values being passed into by the caller:

From what I know if a type parameter is only used for output, you can use out. However if the type is only used for input, you can use in. It's the convenience because the compiler cannot be sure if you can remember which form is called covariance and which is called contravariance. If you don't declare them explicitly once the type has been declared, the relevant types of conversion are available implicitly.

There is no variance (either covariance or contravariance) in classes because even if you have a class that only uses the type parameter for input (or only uses it for output), you can’t specify the in or out modifiers. Only interfaces and delegates can have variant type parameters. Firstly the CLR doesn’t allow it. From the conceptual point of view Interfaces represent a way of looking at an object from a particular perspective, whereas classes are more actual implementation types.

Pomiferous answered 28/6, 2011 at 15:22 Comment(0)
P
7

It means that if you have this:

class Parent { } 
class Child : Parent { }

Then an instance of IFoo<Child> is also an instance of IFoo<Parent>.

Parochial answered 28/6, 2011 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.