У меня проблема с возвратом сбора и ковариации, и мне было интересно, есть ли у кого-нибудь лучшее решение.
Сценарий таков:
У меня есть 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);
Но поскольку он был преобразован в другой список, добавление ничего не будет делать, поскольку элемент не будет добавлен обратно в исходный объект результата.
Я могу придумать несколько решений, таких как:
- Я мог бы синхронизировать два списка, но это, кажется, дополнительная работа
- Вернуть IEnumerable вместо IList и добавить метод для создания / удаления коллекций
- Создание пользовательской коллекции, которая принимает 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.
Я попробую и посмотрю, сработает ли это.