Search code examples
c#inheritanceinterfacecovariancecontravariance

Convert error when there should be none since class implements required interface


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?


Solution

  • 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; }
    }