Как мне поступить в ситуации, когда мне нужно хранить несколько несвязанных типов, но предоставлять конкретные типы по требованию?

StackOverflow https://stackoverflow.com/questions/54219

  •  09-06-2019
  •  | 
  •  

Вопрос

Я работаю над редактором файлов, которые используются важным инструментом внутреннего тестирования, который мы используем.Инструмент сам по себе большой, сложный, а его рефакторинг или переписывание потребует больше ресурсов, чем мы сможем выделить на него в обозримом будущем, поэтому мои руки связаны, когда дело доходит до крупных модификаций.Я должен использовать язык .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).Я думаю, что кэшированная версия немного более разумна, хотя ожидаемые параметры производительности не сделают некэшированную версию заметно медленнее.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top