Variance with generic classes and interfaces in c# (type matching, type guarding)
Actual real life problem
I’ve got a .NET (framework) MVC application (using EpiServer) where most (but not all) views are typed to a Model
of type PageViewModel<T> where T: PageData
. The feature to be implemented is to render a property of PageData
whenever we’re rendering a view that’s typed to PageViewModel<T> where T: PageData
, but do nothing for other views. A instance of T
is available on the model. The property should be rendered in the “root” of the page, and not besides the rest of the view specific rendering.
I see the following solutions to this “problem”:
- Use a
section
in the root layout, and render into it from the sub view. This isn’t an option as the same thing should always be rendered, regardless of which subview it is, using this solution wouldn’t be DRY. - Combine above with a typed parent layout of our subviews. Where the rendering into the section could be handled in the new parent layout. A better solution, but would make developers need to remember to use this layout. Perhaps not a big hassle, and could possibly be abstracted away. Probably the solution that will be used.
- Use a type guard in the root view that render the property in the correct place if the model is of type
PageViewModel<T> where T: PageData
(the model in the root is untyped, making it a dynamic).
Type guarding generic types in c#
The 3rd solution is what got me thinking about variance in C# and how I would handle it in this scenario.
The first approach would be to check if the model is of type PageViewModel<PageData>
, using something similar to var propToRender = (Model as PageViewModel<PageData>)?.InnerModel.Prop
. I mean we know that the generic parameter is always a type derived from PageData
, trying to upcast it like this makes sense right?
The problem is that variance doesn’t work like this in C#, it’s only valid if our model is exactly PageViewModel<T> where T: PageData
. The solution to this is to use a variance modifier when defining our PageViewModel
, the next problem is that variance modifiers aren’t available on generic classes. They are however available on interfaces. The solution being to create the interface IPageViewModel<out T> where T: PageData
and having our PageViewModel
implement it. the out
modifier in PageViewModel<out T>
means that T
is now covariant, meaning that it’s now possible to do var propToRender = (Model as IPageViewModel<PageData>)?.InnerModel.Prop
, which will be valid.
This is something that the compiler will generally help you with, except for these cases where your instance is previously cast to dynamic
or object
.
The output from the contrived example below is the following, highlighting how this works.
- I’m a variant of variance.Container`1[variance.TypeParameterDerived] :)
- I’m not a variant of variance.Container`1[variance.TypeParameterBase] :(
- I’m a variant of variance.IContainer`1[variance.TypeParameterDerived] :)
- I’m a variant of variance.IContainer`1[variance.TypeParameterBase] :)
You may also use the in
keyword to make the generic parameter be contravariant
.