Как мне поступить в ситуации, когда мне нужно хранить несколько несвязанных типов, но предоставлять конкретные типы по требованию?
Вопрос
Я работаю над редактором файлов, которые используются важным инструментом внутреннего тестирования, который мы используем.Инструмент сам по себе большой, сложный, а его рефакторинг или переписывание потребует больше ресурсов, чем мы сможем выделить на него в обозримом будущем, поэтому мои руки связаны, когда дело доходит до крупных модификаций.Я должен использовать язык .NET.
Файлы представляют собой сериализованные в формате XML версии четырех классов, используемых инструментом (назовем их A, B, C и D).Когда все в порядке, классы образуют древовидную структуру.Наш редактор работает, загружая набор файлов, десериализуя их, определяя связи между ними и отслеживая любые обнаруженные некорректные состояния.Идея состоит в том, чтобы мы отказались от редактирования этих файлов вручную, которое приводит к множеству ошибок.
Для определенного типа ошибки я хотел бы сохранить коллекцию всех файлов, в которых возникла проблема.Проблема может возникнуть у всех четырех классов, и мне хотелось бы максимально сократить дублирование кода.Важным требованием является то, что пользователь должен иметь возможность получать предметы в наборах;например, им нужно получить все объекты A с ошибкой, и указание им перебрать всю коллекцию и выбрать то, что они хотят, неприемлемо по сравнению с GetAs()
метод.Итак, моей первой мыслью было создать общий элемент, связывающий десериализованный объект и некоторые метаданные, указывающие на ошибку:
public class ErrorItem<T>
{
public T Item { get; set; }
public Metadata Metadata { get; set; }
}
Затем у меня был бы класс коллекции, который мог бы хранить все элементы ошибок, со вспомогательными методами для извлечения элементов определенного класса, когда они нужны пользователю.Вот здесь-то и начинаются проблемы.
Ни один из классов не наследует от общего предка (кроме Object
).Вероятно, это была ошибка первоначального дизайна, но я провел несколько дней, размышляя об этом, и на самом деле классы не имеют много общего, кроме свойства GUID, которое уникально идентифицирует каждый элемент, поэтому я могу понять, почему первоначальный дизайнер не роднили их по наследству.Это означает, что единая коллекция ошибок должна будет хранить ErrorItem<Object>
объекты, поскольку у меня нет базового класса или интерфейса для ограничения того, что входит.Однако это делает идею этой единой коллекции немного схематичной:
Public Class ErrorCollection
{
public ErrorItem<Object> AllItems { get; set; }
}
Однако это имеет последствия для публичного интерфейса.Чего я действительно хочу, так это вернуть соответствующий ErrorItem
общий тип, например:
public ErrorItem<A>[] GetA()
Это невозможно, потому что я могу хранить только ErrorItem<Object>
!Я продумал несколько обходных путей;в основном они включают создание нового ErrorItem
соответствующего типа на лету, но это просто кажется некрасивым.Другая мысль заключалась в использовании Dictionary
чтобы элементы были организованы по типам, но это все равно кажется неправильным.
Есть ли какая-то закономерность, которая могла бы мне здесь помочь?Я знаю, что самый простой способ решить эту проблему — добавить базовый класс, от которого наследуются A, B, C и D, но я стараюсь как можно меньше влиять на исходный инструмент.Достаточно ли велика стоимость любого обходного пути, чтобы мне пришлось менять исходный инструмент?
Решение
Если A, B, C и D не имеют ничего общего, то добавление базового класса ничего вам не даст.Это будет просто пустой класс и по сути будет таким же, как объект.
Я бы просто создал класс ErrorItem без дженериков, сделал Item объектом и выполнил приведение типов, когда вы хотите использовать указанные объекты.Если вы хотите использовать какие-либо свойства или методы класса A, B, C или D, кроме Guid, вам все равно придется их привести.
Другие советы
Это то, что вы ищите?
private List<ErrorItem<object>> _allObjects = new List<ErrorItem<object>>();
public IEnumerable<ErrorItem<A>> ItemsOfA
{
get
{
foreach (ErrorItem<object> obj in _allObjects)
{
if (obj.Item is A)
yield return new ErrorItem<A>((A)obj.Item, obj.MetaData);
}
}
}
Если вы хотите кэшировать ItemsOfA
вы можете легко это сделать:
private List<ErrorItem<A>> _itemsOfA = null;
public IEnumerable<ErrorItem<A>> ItemsOfACached
{
if (_itemsOfA == null)
_itemsOfA = new List<ErrorItem<A>>(ItemsOfA);
return _itemsOfA;
}
Ответ, который я пока привожу, представляет собой комбинацию ответов от friguybob и Мендельта Сибенги.
Как отметил Мендельт Зибенга, добавление базового класса просто загрязнит пространство имен и создаст аналогичную проблему.Я бы получил больше контроля над тем, какие элементы могут попадать в коллекцию, но мне все равно нужно было бы хранить ErrorItem<BaseClass>
и все еще делаю кастинг, так что у меня будет немного другая проблема с той же основной причиной.Вот почему я выбрал пост в качестве ответа:это указывает на то, что мне придется выполнить некоторые приведения, несмотря ни на что, и KISS будет диктовать, что дополнительный базовый класс и дженерики слишком велики.
Мне нравится ответ friguybob не за само решение, а за напоминание о yield return
, что облегчит написание некешированной версии (я собирался использовать LINQ).Я думаю, что кэшированная версия немного более разумна, хотя ожидаемые параметры производительности не сделают некэшированную версию заметно медленнее.