Ковариация списка приводит к дублированию кода

#c# #abstraction #covariance #code-duplication

#c# #абстракция #ковариация #дублирование кода

Вопрос:

У меня есть 2 независимых класса A и B, и у меня есть класс хранения, который управляет хранением объектов типа A и B.

Я пытаюсь абстрагировать код, который сохраняет A и B, однако я застрял из-за ковариации списка, которую я не смог назначить List<object> objList = new List<A>(); в следующем коде.

  [DataContract]
public class A {
    public int UID;
}

[DataContract]
public class B {
    public int UID;
}

public class Storage {

    public void Store(A a) {
        List<A> aList = ReadA();
        if (aList == null) {
            aList = new List<A>();
        }
        aList.Add(a);
        WriteNodes(aList);
    }

    public void StoreB(B b) {
        List<B> bList = ReadB();
        if (bList == null) {
            bList = new List<B>();
        }
        bList.Add(b);
        WriteNodes(bList);
    }

    public List<A> ReadA() {
        //deserializes from aFileName and returns List<A>
    }

    public List<B> ReadB() {
        //deserializes from bFileName adn returns List<B>
    }

    private static void WriteNodes<T>(List<T> nodeList) {
        FileStream fs = new FileStream(aFileName, FileMode.Create);
        XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(fs);
        DataContractSerializer ser =
            new DataContractSerializer(typeof(List<T>));
        ser.WriteObject(writer, nodeList);
        writer.Close();
        fs.Close();
    }
}
 

Если вы посмотрите на методы StoreA и StoreB, они имеют общий шаблон, за исключением используемого типа. ReadA и ReadB не проблема, я мог бы просто взять тип в качестве другого параметра и создать единую функцию чтения.

Итак, возможно ли создать абстракцию для хранилища, чтобы я не получал методы StoreA и StoreB?

Ответ №1:

Как насчет:

 public void Store<T>(T a) {
        List<T> aList = Read<T>();
        if (aList == null) {
            aList = new List<T>();
        }
        aList.Add(a);
        WriteNodes(aList);
    }

public List<T> Read<T>() {
 //Read a or b depend on T
}
 

Ответ №2:

Да, вы можете сделать это, введя общий интерфейс для элементов, которые нужно создать, и извлечь тип элемента в enum.

Хранение

 public class Storage 
{       
   public Storage()
   {
     // create it once on construction stage
     // so you do not need to check for null each time in Sore()/Read()
     this.AllItems = new List<IItem>();
   }

   public IList<IItem> AllItems { get; private set; }

   public void Store<TItem>(TItem item)  
      where TItem: IItem
   {          
       this.AllItems.Add(item);
   }

   public IEnumerable<IItem> Read(StorageItemType itemType)
   {
      return this.AllItems.Where(item => item.ItemType == itemType);
   }
}
 

Абстрактный тип элемента хранилища (более общее решение):

 // Item types
enum StorageItemType
{
  A,
  B
}

interface IItem
{
   int UID { get; }
   StorageItemType ItemType { get; }
}

public abstract class StorageItemBase: IItem
{
  public int UID { get; private set; }

  public abstract StorageItemType ItemType 
}

public sealed class B : StorageItemBase    
{
  public override StorageItemType ItemType 
  { 
    get 
    {
       return StorageItemType.B; // !!!
    }
  }
}
 

Комментарии:

1. @sII, но хранение происходит с файлом, и я десериализуюсь из файла (т.Е. Я храню объекты типа A в одном файле, а объекты типа B в другом файле). Похоже, что в вашем решении есть объекты в памяти. Поэтому для сохранения в нужный файл мне нужно иметь сопоставление типа объекта с именем файла A->fileA; B->fileB и в методе Store<T>() извлеките имя файла перед фактическим сохранением.

2. Это не проблема, просто сопоставьте usign IDictionary<StoreItemType, string> like <StoreItemType.A, "fileA.xml"> , <StoreItemType.B, "fileB.xml"> а затем usign IDictionary . containsKey разрешить имя файла — if(dictionary.Containskey(StoreItemType.A)) { fileName = dictionary[StoreItemType.A]; }

3. хорошо; понял; Но является ли это стандартным шаблоном для явного сохранения типа объекта (в данном случае StorageItemType) как части самого фактического объекта? Насколько это отличается от typeof() ? Не будет ли typeof предоставлять почти ту же информацию, что и StorageItemType ?

4. StorageItemType — это тип бизнес-элемента, а не тип ОБЪЕКТА, который возвращается object . GetType() так что это довольно обычный метод для инкапсуляции некоторого набора типов с помощью enum