Как добавить известный тип в список <T>? - PullRequest
0 голосов
/ 29 мая 2019

Общая цель здесь такова: у нас в хранилище BLOB-объектов Azure хранится множество файлов CSV с различными именами и форматами.Нам нужно преобразовать их в списки.

У меня есть интерфейс:

public interface IGpasData
{
    List<T> ConvertToList<T>(StreamReader reader);
}

И вот пример класса, который его реализует:

public class GpasTableOfContent : IGpasData
{
    public string TocProp0 { get; set; }
    public string TocProp1 { get; set; }
    public string TocProp2 { get; set; }

    public List<T> ConvertToList<T>(StreamReader reader)
    {
        List<T> dataList = new List<T>();
        while (!reader.EndOfStream)
        {
            var lineItem = reader.ReadLine();
            GpasTableOfContent dataItem = new GpasTableOfContent
            {
                TocProp0 = lineItem.Split(',')[0],
                TocProp1 = lineItem.Split(',')[1],
                Type = lineItem.Split(',')[2]
            };
            dataList.Add(dataItem);
        }
        return dataList;
    }
}

ДляПродолжайте с примером выше класса, есть файл с именем ToC.csv.В классе, который предназначен для преобразования этого файла в список, я выполняю этот вызов:

List<GpasTableOfContent> gpasToCList = ConvertCloudFileToList<GpasTableOfContent>("ToC.csv", "MyModel");

Некоторые другие возможные примеры:

List<GpasFoo> gpasFooList = ConvertCloudFileToList<GpasFoo>("foo.csv", "MyModel");

List<GpasBar> gpasBarList = ConvertCloudFileToList<GpasBar>("bar.csv", "MyModel");

Вот ConvertCloudFileToList:

private List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
    {
        // Get the .csv file from the InProgress Directory
        string filePath = $"{modelName}/{fileName}";
        CloudFile cloudFile = _inProgressDir.GetFileReference(filePath);

        List<T> dataList = new List<T>();

        // Does the file exist?
        if (!cloudFile.Exists())
            return dataList;

        using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
        {
            IGpasData gpasData = (IGpasData)Activator.CreateInstance<T>();
            dataList = gpasData.ConvertToList<T>(reader);
        }

        return dataList;
    }

И это возвращает нас к ConvertToList.Проблема здесь:

dataList.Add(dataItem);

Невозможно преобразовать 'GpasFoo' в 'T'

Не уверен, как обойти это.

Ответы [ 3 ]

1 голос
/ 29 мая 2019

Как уже отмечалось, этот дизайн имеет недостатки:

public interface IGpasData
{
   List<T> ConvertToList<T>(StreamReader reader);
}

В этом контракте говорится, что IGpasData должен знать только, как десериализовать что-либо. Это не имеет смысла.

IGpasData должен знать, как десериализовать себя, и для этого нам понадобится самоссылающийся интерфейс:

public interface IGpasData<T> where T : IGpasData<T>
{
    List<T> ConvertToList(StreamReader reader);
}

public class GpasBar: IGpasData<GpasBar>
{
    public string MyPropertyA { get; set; }
    public int MyPropertyB { get; set; }

    public List<GpasBar> ConvertToList(StreamReader reader)
    {
        var results = new List<GpasBar>();
        while (!reader.EndOfStream)
        {
            var values = reader.ReadLine().Split(',');
            results.Add(new GpasBar()
            {
                PropertyA = values[0],
                PropertyB = int.Parse(values[1]),
            });
        }
        return results;
    }
}

Или IGpasData должен знать, как заполнить себя массивом значений:

public interface IGpasData
{
    void Populate(string[] values);
}
public class GpasBar
{
    public string MyPropertyA { get; set; }
    public int MyPropertyB { get; set; }

    public void Populate(string[] values)
    {
        MyPropertyA = values[0];
        MyPropertyB = int.Parse(values[1]);
    }
}

public static List<T> ConvertCloudFileToList<T>(string fileName, string modelName)
    where T : IGpasData, new()
{
    // ...
    using (StreamReader reader = new StreamReader(cloudFile.OpenRead()))
    {
        var results = new List<T>();
        while (!reader.EndOfStream)
        {
            var item = new T();
            item.Populate(reader.ReadLine().Split(','));

            results.Add(item);
        }
        return results;
    }
}

Используя этот второй подход, вы можете избежать дублирования части о StreamReader и чтения строк.

1 голос
/ 29 мая 2019

Предполагается, что любой объект, который является IGpasData, сможет создавать Список любого данного типа, если ему предоставляется StreamReader.GpasTableOfContent не соответствует этому требованию, он может создавать только список своего типа.

Однако не представляется разумным, чтобы один тип GpasData отвечал за преобразование всего, поэтому я бы предложил переместитьаргумент типа из метода ConvertToList в интерфейс.Таким образом, подклассы будут отвечать только за преобразование списков определенного типа.

public interface IGpasData<T>
{
    List<T> ConvertToList(StreamReader reader);
}

public class GpasTableOfContent : IGpasData<GpasTableOfContent>
{
    //...

    public List<GpasTableOfContent> ConvertToList(StreamReader reader)
    {
        //...
    }
}

В примечании, создающем пустое оглавление, а затем используем его для чтения из потока и созданияСписок реального содержания мне кажется очень неуклюжим.По моему мнению, поведение создания этих объектов содержимого должно быть перенесено в свой собственный класс.

1 голос
/ 29 мая 2019

Вы не можете делать то, что вы хотите здесь, не предоставив дополнительную логику.Проблема в том, что у вас есть строка для чтения файла CSV, и вы хотите преобразовать ее в T, но нет правила для преобразования строки в любой произвольный тип.

Один из подходов состоит в том, чтобы изменитьметод для получения делегата Func, который используется для преобразования каждой строки в T. Тогда, если, например, ваши данные гарантированно состоят из двойников, вы можете передать t => Double.Parse (t) для этого аргумента.Конечно, этот подход требует, чтобы вы изменили сигнатуру метода интерфейса, который вы реализуете.

Если вы не можете изменить сигнатуру метода интерфейса, то все, что я могу предложить, это попытаться обработать предварительно-определенный набор типов и выдача исключения для других типов.

...