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
sectionin 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
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