Есть ли тип, который может быть назначен как из списка <T>, так и из коллекции <T>в C #? - PullRequest
0 голосов
/ 10 октября 2019

У меня есть следующие два класса в C #:

class DbModel{
    public List<string> Values {get; set;}
    //...other proerties...
}

и

class ViewModel{
    public ObservableCollection<string> Values {get; set;}
    //...other proerties...
}

Я хотел бы создать общий Interface, который реализуют оба класса, поэтому я могу передатьлюбой из двух в некоторых методах. Это вообще возможно? Я пробовал что-то вроде:

public interface IModel{
    IEnumerable<string> Values {get; set;}
}

, но это не работает, даже если List и ObservableCollection<string> можно назначить на IEnumerable<string>.

Есть ли способ обойти это? Какой будет лучший обходной путь (если есть)?

Ответы [ 2 ]

2 голосов
/ 10 октября 2019

Я пробовал это раньше, создавая красивые «доменные» интерфейсы, которые вы применяете к объектам всех видов, так что вы можете писать код, который работает на интерфейсе, а не на объекте домена, но вы очень быстро столкнетесь с проблемами,

Для начала, когда класс реализует интерфейс, он должен точно соответствовать типам членов интерфейса. Конечно, и List<T>, и ObservableCollection<T> реализуют IEnumerable<T>, но это не значит, что они оба удовлетворяют декларации этого члена интерфейса.

Так что вам придется реализовать этот конкретный тип. Вы можете сделать это, например, используя явную реализацию интерфейса. Но у члена интерфейса есть как метод получения, так и метод установки:

class DbModel : IModel
{
    public List<string> Values {get; set;}

    IEnumerable<string> IModel.Values
    {
        get { return Values; }
        set
        {
            Values = ... // now what?
        }
    }
}

class ViewModel : IModel
{
    public ObservableCollection<string> Values {get; set;}

    IEnumerable<string> IModel.Values
    {
        get { return Values; }
        set
        {
            Values = ... // now what?
        }
    }
}

Возможно, нужен метод установки, потому что вы хотите, чтобы какой-то общий код мог назначать коллекцию Values. И IEnumerable<T> не очень полезен ни для чего, кроме перечисления.

Так что вам придется преобразовать входящий values в тип, соответствующий классу в установщике. Например Values = values.ToList() в первом.

И тогда у вас есть вызывающий абонент:

IModel dbModel = new DbModel();

var values = new List<string> { "foo", "bar" };

dbModel.Values = values;

values.Add("baz");

Что будет dbModel.Values после этого кода? Подсказка: он не будет содержать "baz". Не используйте этот подход, он вас укусит.

Забудьте об общем интерфейсе. Определите реальную проблему, которую должен решить интерфейс, и решите ее другим способом.

2 голосов
/ 10 октября 2019

C # не имеет ковариантных типов возврата (хотя они были предложены ). Поэтому вам нужно использовать явные реализации интерфейса (как уже упоминалось Лассе).

Возможно, вы не хотите, чтобы ваше свойство Values могло быть установлено. Если он настраиваемый, то любой может установить любой тип, который реализует IEnumerable<string>, даже если базовый ViewModel / DbModel поддерживает только очень специфический тип.

public interface IModel
{
    IEnumerable<string> Values { get; }
}

public class ViewModel : IModel
{
    public ObservableCollection<string> Values { get; set; }
    IEnumerable<string> IModel.Values => Values;
}
...