0

I have an interface as follows:

public interface IPageViewModel<T> where T : class
{
    string ViewName { get; set; }
    T SelectedItem { get; set; }
    List<T> ItemsList { get; set; }
}

Then, I have two classes:

internal class TestViewModel : IPageViewModel<INotifyPropertyChanged> //let's skip the fact that T is supposed to be a class while it somehow compiles and works with an interface...

internal class HardwareViewModel : IPageViewModel<Hardware>

Where Hardware is:

public class Hardware : NotificationObject

And NotificationObject is:

public class NotificationObject : INotifyPropertyChanged

And finally, I have a class as follows:

internal class NavigationViewModel
{
    public List<IPageViewModel<INotifyPropertyChanged>> PageViewModelsList { get; set; } = new List<IPageViewModel<INotifyPropertyChanged>>();

    public NavigationViewModel()
    {
        PageViewModelsList.Add(new TestViewModel());
        PageViewModelsList.Add(new HardwareViewModel()); //error
    }
}

Now, the problem is: while the first line in constructor compiles fine, the second one throws an error: cannot convert from ViewModels.HardwareViewModel to Helpers.IPageViewModel<System.Component.INotifyPropertyChanged>.
But this makes no sense. Hardware inherits from NotificationObject which implements INotifyPropertyChanged so IPageViewModel<Hardware> === IPageViewModel<INotifyPropertyChanged>. Can anyone please explain why there's an error?

Sazu
  • 11
  • 5
  • Firstly `class` constraints just constrain `T` to be a reference type, which all interfaces are. Your `IPageViewModel` is invariant in `T` so `IPageViewModel` is not a subtype of `IPageViewModel`. Given your current definition, you can't make it covariant unless you split it up. – Lee May 03 '18 at 20:39
  • Well, `IPageViewModel` is not a subtype of `IPageViewModel`, in fact they are completely different types that don´t have anything in common. Your `IPageViewModel`-interface needs to be co-variant to allow this, see https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance – MakePeaceGreatAgain May 03 '18 at 20:42
  • @Lee Huh, I always thought `class` constraint forces a reference type. Always good to learn sth new I guess, thanks. @HimBromBeere Thanks for the link, I've never really understood the variance topic, so I'm off into some reading, thanks – Sazu May 03 '18 at 21:01
  • @Lee after reading through some variance explanations, are you sure `IPageViewModel` is invariant? From what I've read, it should be contravariant. And by the way, do I understand variance correctly: my solutions to this problem are either removing `` along with `SelectedItem` and `ItemsList` from the interface or changing it into `IPageViewModel`? – Sazu May 06 '18 at 10:39
  • `out T` would make `IPageViewModel` covariant, not contravariant, however you won't be able to do it due to the setters for the `SelectedItem` and `ItemsList` properties. If you remove the setters and change the type of `ItemsList` to `IEnumerable` (or some other covariant collection like `IReadOnlyList`) then you can make `IPageViewModel` covariant. – Lee May 06 '18 at 11:20
  • When I said contravariant instead of invariant, I was referring to `IPageViewModel`, not to `IPageViewModel` – Sazu May 06 '18 at 15:08

1 Answers1

0

Thanks to the comments I've realized the topic causing these problems here is called 'variance'. So after reading a bit about it I decided to go with this solution:

public interface IPageViewModel
{
    string ViewName { get; set; }
}

But if someone wanted to keep these fields and keep their interfaces covariant, it'd have to look something like this:

public interface IPageViewModel<out T> where T : class
{
    string ViewName { get; set; }
    T SelectedItem { get; }
    IEnumerable<T> ItemsList { get; }
}
Sazu
  • 11
  • 5