Наследование generic-класса от интерфейса

486
07 февраля 2017, 18:35

У меня есть классы и интерфейсы:

public interface IEntity
{
    int ID { get; set; }
}
public class Entity : IEntity
{
    public int ID { get; set; }
}
public interface IEntityListViewModel
{
    RangeObservableCollection<IEntity> Items { get; set; }
    IEntity SelectedItem { get; set; }
    void LoadItems();
}

Теперь мне нужен такой класс:

public abstract class EntityListViewModel<T> : IEntityListViewModel where T :  IEntity
{
    public RangeObservableCollection<T> Items { get; set; }
    public T SelectedItem { get; set; }
    public EntityListViewModel()
    {
        Items = new RangeObservableCollection<T>();
    }
    protected abstract List<T> GetEntities();
    public void LoadItems()
    {
        var lst = GetEntities();
        Items.ReplaceRange(lst);
    }
}

Естественно, компилятор требует реализации RangeObservableCollection.Items

Я бы мог сделать так:

public interface IEntityListViewModel<T> where T : IEntity
{
    RangeObservableCollection<T> Items { get; set; }
    T SelectedItem { get; set; }
    void LoadItems();
}

Но у меня есть еще один класс:

public abstract class UserControlBase : UserControl
{
    public IEntityListViewModel VM { get; set; }        
    public virtual void Page_Loaded(object sender, RoutedEventArgs e)
    {
        VM.LoadItems();
    }        
}

А дальше уже конкретные UserControl'ы наследуются от UserControlBase. Для чего это мне? В UserControl'ах куча повторяющегося кода (в основном привязки событий ViewModel). Поэтому это все я хочу сделать в UserControlBase. Я бы мог объявить в UserControlBase так:

public IEntityListViewModel<T> VM { get; set; }

но тогда мне T нужно поднимать в UserControlBase, а это как-то криво.

Как мне наследовать EntityListViewModel от обычного IEntityListViewModel? Или есть какое-то другое решение?

UPD 1: Явно реализовал, как посоветовали в комментариях:

public abstract class EntityListViewModel<T> : IEntityListViewModel where T :  IEntity
{
    public RangeObservableCollection<T> Items { get; set; }
    public T SelectedItem { get; set; }
    RangeObservableCollection<IEntity> IEntityListViewModel.Items { get; set; }
    IEntity IEntityListViewModel.SelectedItem { get; set; }
    public EntityListViewModel()
    {
        Items = new RangeObservableCollection<T>();
    }
    protected abstract List<T> GetEntities();
    public void LoadItems()
    {
        var lst = GetEntities();
        Items.ReplaceRange(lst);
    }
}

Но теперь во View вызов VM.Items обращается к IEntityListViewModel.Items

Answer 1

В текущем виде самым простым для вас было бы действительно объявить UserControlBase обобщенным. Если же этот способ вам по какой-то причине не подойдет - надо выносить хранение из базового класса в наследники.

В вашем случае, если базовый класс подписывается на события - ему нужен интерфейс INotifyCollectionChanged. Его и надо запрашивать у наследника:

public abstract class UserControlBase : UserControl
{
    protected abstract INotifyCollectionChanged AbstractItems { get; }
}
public class FooListControl : UserControlBase
{
    public IEntityListViewModel<Foo> VM { get; set; }
    protected virtual INotifyCollectionChanged AbstractItems => VM.Items;
}

Если при таком подходе в базовом классе оказывается слишком много полей - надо выделять в интерфейсе ковариантную часть.

Например, для ваш интерфейс IEntityListViewModel можно разбить следующим образом:

public interface IEntityListViewModel<T> : IEntityListViewModelOut<T> where T : IEntity
{
    RangeObservableCollection<T> Items { get; set; }
    T SelectedItem { get; set; }
}
public interface IEntityListViewModelOut<out T> where T : IEntity
{
    IReadOnlyList<T> Items { get; }
    INotifyCollectionChanged ItemsEvents { get; }
    T SelectedItem { get; }
    void LoadItems();
}

Реализовать такой "усложненный" интерфейс не сильно сложнее чем обычный:

public class EntityListViewModel<T> : IEntityListViewModel<T> where T :  IEntity
{
    public RangeObservableCollection<T> Items { get; set; } = new RangeObservableCollection<T>();
    public T SelectedItem { get; set; }
    IEntityListViewModelOut<T>.Items => Items;
    IEntityListViewModelOut<T>.ItemsEvents => Items;
    IEntityListViewModelOut<T>.SelectedItem => SelectedItem;
}

После этого, ковариантную часть можно "пробросить" в базовый класс через абстрактное свойство:

public abstract class UserControlBase : UserControl
{
    protected abstract IEntityListViewModelOut<IEntity> AbstractVM { get; }
}
public class FooListControl : UserControlBase
{
    public IEntityListViewModel<Foo> VM { get; set; }
    protected virtual IEntityListViewModelOut<IEntity> AbstractVM => VM;
}
READ ALSO
Символ юникода для имени хоста

Символ юникода для имени хоста

Могу ли я каким то образом, запустив httpListener по префиксу "http://+:8080", после этого послать на него запрос? Или же символы юникода нельзя использовать...

478
Скопировать часть байтов из файла c#

Скопировать часть байтов из файла c#

Есть файл 3exe, в котором записаны еще 2 файла : 1

494
Как поместить таблицу в кэш?

Как поместить таблицу в кэш?

Имеются 3 таблицы в MS SQL:

506