Как бороться с ковариацией при возврате коллекции в c #? - PullRequest
9 голосов
/ 11 сентября 2011

У меня проблема с возвратом сбора и ковариации, и мне было интересно, есть ли у кого-нибудь лучшее решение.

Сценарий таков:

У меня есть 2 версии реализации, и я хотел бы сохранить реализацию версии полностью отдельной (даже если они могут иметь одинаковую логику). В реализации я хотел бы вернуть список элементов и, следовательно, в интерфейсе я бы возвратил список интерфейса элемента. Однако в реальной реализации интерфейса я хотел бы вернуть конкретный объект элемента. В коде это выглядит примерно так:

interface IItem
{
    // some properties here
}

interface IResult
{
    IList<IItem> Items { get; }
}

Тогда было бы 2 пространства имен, которые имеют конкретную реализацию этих интерфейсов. Например,

Namespace Version1

class Item : IItem

class Result : IResult
{
    public List<Item> Items
    {
        get { // get the list from somewhere }
    }

    IList<IItem> IResult.Items
    {
        get
        {
            // due to covariance, i have to convert it
            return this.Items.ToList<IItem>();
        }
    }
}

Будет другая реализация того же самого в пространстве имен Version2.

Чтобы создать эти объекты, будет фабрика, которая берет версию и создает соответствующий тип бетона при необходимости.

Если вызывающая сторона знает точную версию и выполняет следующее, код работает нормально

Version1.Result result = new Version1.Result();
result.Items.Add(//something);

Однако я бы хотел, чтобы пользователь мог делать что-то подобное.

IResult result = // create from factory
result.Items.Add(//something);

Но поскольку он был преобразован в другой список, добавление ничего не будет делать, поскольку элемент не будет добавлен обратно в исходный объект результата.

Я могу придумать несколько решений, таких как:

  1. Я мог бы синхронизировать два списка, но это, кажется, дополнительная работа
  2. Вернуть IEnumerable вместо IList и добавить метод для создания / удаления коллекций
  3. Создание пользовательской коллекции, которая принимает TConcrete и TInterface

Я понимаю, почему это происходит (из-за типа safe и все такое), но я думаю, что ни один из обходных путей не выглядит очень элегантным. У кого-нибудь есть лучшие решения или предложения?

Заранее спасибо!

Обновление

Подумав об этом больше, я думаю, что могу сделать следующее:

public interface ICustomCollection<TInterface> : ICollection<TInterface>
{
}

public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface
{
    public void Add(TConcrete item)
    {
        // do add
    }

    void ICustomCollection<TInterface>.Add(TInterface item)
    {
        // validate that item is TConcrete and add to the collection.
        // otherwise throw exception indicating that the add is not allowed due to incompatible type
    }

    // rest of the implementation
}

тогда я могу иметь

interface IResult
{
    ICustomCollection<IItem> Items { get; }
}

then for implementation, I will have

class Result : IResult
{
    public CustomCollection<Item, IItem> Items { get; }

    ICustomCollection<TItem> IResult.Items
    {
        get { return this.Items; }
    }
}

таким образом, если вызывающий обращается к классу Result, он будет проходить через CustomCollection.Add (элемент TConcrete), который уже является TConcrete. Если вызывающая сторона осуществляет доступ через интерфейс IResult, она будет проходить через customCollection.Add (элемент TInterface), и произойдет проверка и убедитесь, что тип действительно TConcrete.

Я попробую и посмотрю, сработает ли это.

Ответы [ 3 ]

2 голосов
/ 11 сентября 2011

Проблема, с которой вы сталкиваетесь, заключается в том, что вы хотите показать тип, который говорит (среди прочего) «вы можете добавить ко мне любое IItem», но на самом деле это будет «Вы можете добавить только Item s».мне".Я думаю, что лучшим решением было бы разоблачить IList<Item>.Код, который использует это, должен знать о конкретном Item в любом случае, если он должен добавить их в список.

Но если вы действительно хотите это сделать, я думаю, что самым чистым решением будет 3.если я вас правильно понимаю.Это будет обертка вокруг IList<TConcrete> и она будет реализовывать IList<TInterface>.Если вы попытаетесь вставить в него что-то, отличное от TConcrete, оно выдаст исключение.

0 голосов
/ 12 сентября 2011

Проблема в том, что Microsoft использует один интерфейс, IList , для описания как коллекций, которые могут быть добавлены, так и коллекций, которые не могут. Хотя теоретически было бы возможно для класса реализовать IList в изменяемой форме, а также реализовать IList неизменным образом (свойство ReadOnly первого интерфейса возвращало бы false, а второе возвращало бы true), для этого нет никакого способа класс для указания того, что он реализует IList одним способом, а IList , где cat: T, другим способом. Хотелось бы, чтобы Microsoft сделала так, чтобы IList List реализовал IReadableByIndex , IWritableByIndex , IReadWriteByIndex , IAppendable и ICountable, поскольку они позволили бы использовать ковариацию и противоположность, но они этого не сделали. Может быть полезно реализовать такие интерфейсы самостоятельно и определить оболочки для них, в зависимости от степени, в которой ковариация будет полезной.

0 голосов
/ 11 сентября 2011

+ 1 к комментарию Брента: вернуть неизменяемую коллекцию и предоставить методы модификации для классов.

Просто чтобы повторить, почему вы не можете заставить 1 работать объяснимым образом:

Если вы попытаетесь добавить к List<Item> элемент, который просто реализует IItem, но не относится к типу (или является производным от) элемента, вы не сможете сохранить этот новый элемент в списке. В результате поведение интерфейса будет очень противоречивым - некоторые элементы, которые реализуют IItem, могут быть добавлены нормально, sime завершится ошибкой, и когда вы измените реализацию на version2, поведение также будет изменено.

Простым решением было бы хранить IList<IItem> вместо List<Item>, но разоблачение собраний напрямую требует тщательного обдумывания.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...