Covariance And Contravariance In C#
- Covariance And Contravariance In C# Youtube
- Covariance And Contravariance In Delegates C#
- Covariance And Contravariance C# Classes
C’s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.” The problem in the authors 'Animal doctor' example is that the universe of all possible animal types is practically unbounded, or 'open'. Covariance and contravariance are fully supported in both C and C for implicit conversions involving the const qualifier. Here’s covariance. (Remember that in C, when we want to produce something, we use an out-parameter — we “pass by pointer.”). C# the language, right from the very beginning is always supported covariance and contravariance but in most simple scenario, now in C# 4.0 we have full support of covariance and contravariance in all circumstances, generic interface and generic delegates, other C# types already support from first version of C#.

Today I’d like to try to explain covariance and contravariance, and the many places wesee that notion pop up in C++.
Covariance and contravariance, by example
Consider the following veterinary analogy.We have the notion of an animal. All animals can make noise.Cats are animals. Horses are animals that you can ride.
Crucially, it is part of the static type system that you can’tride cats. It’s not just that you try and it throws an exception, orsomething; cats literally don’t have a ride
method. Okay?
Now we add a producer of animals; let’s say, an animal breeder.(I was going to say “a mama animal,” but then we’d have to decidewhether to express that a MamaCat
was both a MamaAnimal
and a Cat
,and that way lies madness.)
Notice that a cat breeder IS-AN animal breeder. If you say to me,“Quick, I need the phone number of someone who can produce me an animal!”and I give you the number of a cat breeder, you will be happy.(That’s logic!)
Because a cat IS-AN animal, any breeder that produces cats by definitionIS-A breeder that produces animals. We say that the relationship betweenAnimal
and Cat
is covariant with the relationship betweenAnimalBreeder
and CatBreeder
.
Notice that a CatBreeder
only ever produces Cat
s, and aHorseBreeder
only ever produces Horse
s. Conveniently, C++ permits usto encode that extra snippet of information into our source code.
This is a feature known as “covariant return types.”
Now consider what happens if you already have a Cat
, and you come tome and say, “Quick, I need the phone number of someone who can treat mysick cat!” I’d try to find you an animal doctor.
(Footnote: My lovely wife, the namesake of DoctorKeat
, would also have atreat(Dog *)
method at least. But for the purposes of this example,we’ll stick with cats.)
According to the static type system, Dr. Keat treats only cats, andDr. Hackenbush treats onlyhorses. Whereas, of course, Dr. Dolittlecan treat anything!
What does this mean for our class hierarchy? Well, consider Horse
.A horse can do anything a generic animal can do, plus more (namely, be ridden),so we made Horse
inherit from Animal
. DoctorDolittle
can do anythingDoctorHackenbush
can do, plus more, so logically DoctorDolittle
should inheritfrom DoctorHackenbush
.
Taken literally, this would lead to a bit of a mess, because DoctorDolittle
would have to multiply inherit from DoctorHackenbush
andDoctorKeat
and a bunch more animal-doctor classes that we don’t even know about yet.Which is crazy. So let’s back off and just consider Dolittle and Hackenbushin isolation.
This hierarchy is logically correct. If you come to me and say,“I need the number of someone to treat my sick horse!” then you will behappy with any HorseDoctor
; but if you need a horse doctor to treat yoursick cat, you’ll need a more capable horse doctor. If it helps, considerthat an AnimalDoctor
like Dr. Dolittle is also a CatAndHorseDoctor
, whichsounds more like a proper subclass of HorseDoctor
.
We say that the relationship between Animal
and Horse
is contravariant withthe relationship between AnimalDoctor
and HorseDoctor
: an AnimalDoctor
IS-A HorseDoctor
precisely because a Horse
IS-AN Animal
.
Perhaps inconveniently, C++ does not permit us to write the function markedhmm...
above. C++’s classical OOP system supports “covariant return types,” but itdoes not support “contravariant parameter types.”
This concludes our explanation of covariance and contravariance in the classicalOOP system. Now let’s look at other places the notion shows up in C++.
In std::function
(Thanks to Michał Dominiak for givingthis example.)
The C++ core language may not support contravariant parameter types, but thestandard library does, in the form of std::function
and its many implicitconversions. Example:
We see that dolittle
is an acceptable AnimalDoctor
and thus also a HorseDoctor
;whereas hackenbush
is nothing more than a HorseDoctor
.
Similarly, std::function
supports covariant return types. Example:
If you say to me, “Quick, I need the phone number of someone who can produce me an animal!”and I give you the number of Dr. Moreau, you will be happy.(That’s logic!) But if you need specifically a horse, I should send you to a more capablebreeder, who is not only able to produce you an animal but also to give you compile-timeassurance that it is actually a horse. I’d better not send you to Moreau.With Moreau, you never know what you’re going to get.
In const
-correctness
Covariance and contravariance are fully supported in both C and C++for implicit conversions involving the const
qualifier.Here’s covariance.
(Remember that in C, when we want to produce something, we usean out-parameter — we “pass by pointer.”)
This snippet is logically correct! A horse is a more capable animal.In our classical OOP example, a horse was an animal you could ride()
.In this example, a horse is an animal whose target you can ++
.(That is, a mutable int is a more capable const int. Rust got it right!)
Now you come to me and say, “Quick, I need the phone number of someonewho can produce me a horse! It’s a gift for my daughter.”
I can send your empty gift box over to Ben Ishak, whom I trust tofill it with a horse. I can’t send your box to Moreau; he’ll probablyfill it with some other kind of animal. With Moreau, you never know whatyou’re going to get.
(Incidentally, it is super important that the compiler does enforce thisrule for us. If moreau(&giftbox)
were permitted to compile,then on Christmas morning your daughter would probably try to++*giftbox
and end up riding that tiger by mistake.)
That’s covariance: the relationship between int const *
and int *
is covariant with the relationship between void(int const **)
and void(int **)
. Now for contravariance:
If you come to me asking for someone to treat your cat (an Animal
that is not a Horse
), I can send you to Dolittle but not to Hackenbush:
That’s contravariance: the relationship between int const *
and int *
is contravariant with the relationship between void(int const *)
and void(int *)
.
In non-type template parameters (C++17)
In C++17, non-type template parameters (NTTPs) demonstrate contravariance:
Now you
come and say to me, “Quick, I need someone who can treat my sick cat!”I can send you to Dr. Dolittle, but not to Dr. Hackenbush:
(GCC, Clang, and ICC agree on this point.MSVC believes it’d be fine to send you to Hackenbush.)
For the covariant case, we can use our “gift box” metaphor:
In this case, not a single compiler detects the problem withmoreau<your_giftbox>
: everyone freely instantiates moreau
and lets Moreau place a &tiger
inside the Giftbox
, which results in a hard error.
In non-type template parameters with alias templates
By the way, we can replace dolittle
, hackenbush
, and your_giftbox
with alias templates;it doesn’t change any compiler’s behavior.(MSVC still incorrectly accepts you<hackenbush>
;everyone still incorrectly accepts moreau<your_giftbox>
.)
In this case, every compiler does give a hard error at the point of instantiation of Giftbox<&tiger>
,which is refreshing — but nobody detects that your_giftbox
should never have been given to moreau
in the first place.
In constrained template type parameters (C++2a)
The C++2a Working Draft permits template type parameters to be constrained,and even gives the programmer a shorthand syntax which (unfortunately? fortunately?)makes the following example look almost identical to the NTTP example above.
Now you
come and say to me, “Quick, I need someone who can treat my sick cat!”I can send you to Dr. Dolittle, but not to Dr. Hackenbush:
(GCC and Clang agree on this point.)
For the covariant case, we can use our “gift box” metaphor:
GCC and Clang agree that ben_ishak<your_giftbox>
is acceptable and moreau<your_giftbox>
is unacceptable.
In constrained template type parameters with alias templates
By the way, we can replace dolittle
, hackenbush
, and your_giftbox
with alias templates;it doesn’t change any compiler’ behavior.
In variadic template parameters
A variadic parameter list (that could have any number of parameters) is like anAnimal
; a parameter list constrained to take only two parameters is like a Horse
.(A horse IS-AN animal, but not all animals are horses.)
Now you
come and say to me, “Quick, I need someone who can treat my sick cat!”I should be able to send you to Dr. Dolittle, but not to Dr. Hackenbush:
(Here, Clang falters by incorrectly rejectingyou<dolittle>
. GCC, ICC and MSVC accept it.Everybody correctly rejects you<hackenbush>
.)
For the covariant case, we can use our “gift box” metaphor:
In this case, not a single compiler detects the problem withmoreau<your_giftbox>
: everyone freely gives your_giftbox
to moreau
and lets Moreau tryto place a tiger inside it, which results in a hard error.
In variadic template parameters with alias templates
You might think this shouldn’t be different (and you’d be right that it shouldn’t), butin fact we do see a change in MSVC’s behavior…
With hackenbush
expressed as an alias template instead of a class template,MSVC incorrectly acceptsyou<hackenbush>
!Clang continues to incorrectly reject you<dolittle>
.And nobody notices Moreau taking the giftbox until it’s too late.
More?
What are some more places that covariance and contravariance show up in C++?Think of other ways to smuggle “producers” (like moreau
and ben_ishak
) and“consumers” (like hackenbush
and dolittle
) into the language… and if you thinkof a good one, tell me about it!
The “implementation variance” on some of these variadic-template examples isthe subject of CWG1430.
> C++’s classical OOP system supports “covariant return types,” but it does not support “contravariant parameter types.”Covariance And Contravariance In C# Youtube
The problem in the authors 'Animal doctor' example is that the universe of all possible animal types is practically unbounded, or 'open'.
What you want here are 'open multimethods': something Bjarne Stroustrup has wanted in C++ for a long time[0] but have been implemented in library form[1].
Of course, the thing to remember is that, as soon as you go to an open multiple dispatch arrangement, you will lose much of the static safety guarantees that the type-system affords us. There will always be runtime/dynamic type checking, and a runtime failure case.
Covariance And Contravariance In Delegates C#
If you don't want an open system, then some combination of templates for static polymorphism and strategic type-erasure will get the job done. e.g. use boost::variant and multivisitation [2], and the compiler will instantiate all O(n^2) methods for you.
Covariance And Contravariance C# Classes
[0] http://www.stroustrup.com/multimethods.pdf (2007)
[1] https://github.com/jll63/yomm2/blob/master/examples/asteroid...
[2] https://www.boost.org/doc/libs/1_69_0/doc/html/variant/tutor...