Как я могу провести рефакторинг этого C# кода, в настоящее время использующего Dictionarys, чтобы иметь еще меньшую избыточность и быть более безопасным для типов? - PullRequest
1 голос
/ 29 апреля 2020

Из-за бизнес-решений, которые превышают мой тариф, мне необходимо проанализировать и объединить несколько XML файлов.

Чтобы сократить избыточный код, у меня есть эта карта:

        private static readonly Dictionary<string, Type> listTypeByFileName = new Dictionary<string, Type> {
            {"a.xml", typeof(List<A>)},
            {"b.xml", typeof(List<B>)},
            {"c.xml", typeof(List<C>)},
            {"d.xml", typeof(List<D>)},
            // etc.
        };

Поскольку, как эта карта используется, после загрузки и анализа всех XML-файлов результат имеет тип Dictionary<string, object>, где key совпадает с ключами на приведенной выше карте, а value имеет тип, указанный на карте, в результате выполнения этого кода с помощью DownloadFiles (config):

        private static Dictionary<string, object> DownloadFiles(IConfigurationRoot config) {
            Dictionary<string, object> dataListByFileNames = new Dictionary<string, object>();
            listTypeByFileName.Keys.ToList()
                .ForEach(name => dataListByFileNames.Add(name, DownloadData(name, config)));
            return dataListByFileNames;
        }

        private static object DownloadData(string name, IConfigurationRoot config) {
            _ = listTypeByFileName.TryGetValue(name, out Type listType);
            return new XmlSerializer(listType, new XmlRootAttribute("Document"))
                .Deserialize(new StringReader(DownloadFromBlobStorage(name, config).ToString()));
        }

        private static CloudBlockBlob DownloadFromBlobStorage(string filetoDownload, IConfigurationRoot config) {
            return CloudStorageAccount.Parse(config["AzureWebJobsStorage"])
                .CreateCloudBlobClient()
                .GetContainerReference(config["BlobStorageContainerName"])
                .GetBlockBlobReference(filetoDownload);

Первый вопрос: есть ли способ сделать возврат более безопасным? Возможно использование параметризованных типов?

Вторая часть проблемы на самом деле потребляет это Dictionary.

Для каждого типа в этом Dictionary мне теперь нужна функция типа:

        private void AddA(Dictionary<string, object> dataByFileNames) {
            if (dataByFileNames.TryGetValue("a.xml", out object data)) {
                List<A> aList = (List<A>)data;
                aList.ForEach(a =>
                    doSomethingWithA(a);
                );
            }
        }

        private void AddB(Dictionary<string, object> dataByFileNames) {
            if (dataByFileNames.TryGetValue("b.xml", out object data)) {
                List<B> bList = (List<B>)data;
                bList.ForEach(b =>
                    doSomethingWithB(b);
                );
            }
        }

       // etc.

Поскольку у меня уже есть список имен файлов для типов (начало этого вопроса), я чувствую, что должен быть какой-то способ абстрагировать вышеупомянутое, чтобы его не нужно было повторять снова и снова и снова. Обратите внимание, что может быть важно, что каждый тип (A, B, C, D и т. Д. c. Все имеют свойство string Id, которое определенно будет необходимо для всех методов doStringWithX() ... если это полезно, Я могу создать интерфейс, чтобы получить это. Это нормально, если мне нужно привести к нужному типу в каждом doStringWithX() или при вызове каждого из этих методов. c

1 Ответ

1 голос
/ 29 апреля 2020

Во-первых, вместо сохранения типа List<T> в словаре, просто сохраните базовый тип generi c:

private static readonly Dictionary<string, Type> listTypeByFileName = new Dictionary<string, Type> {
    {"a.xml", typeof(A)},
    {"b.xml", typeof(B)}
    // etc.

Это немного облегчит будущие шаги. При десериализации создайте тип списка generi c. После получения типа из словаря вы можете выполнить:

var listType = typeof(List<>).MakeGenericType(typeRetrievedFromDictionary);

После того, как вы десериализовали его, присвойте ему значение IList. Это фактически приводит его к списку object. Это нормально. Поскольку вы десериализовали, используя указанный тип c, каждый элемент в списке будет иметь ожидаемый тип.

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

Dictionary<Type, Action<object>> methodsToInvokeByType;

Добавление методов в словарь:

doSometingMethods.Add(typeof(A), dataItem => DoSomethingWithA((A)dataItem));
doSometingMethods.Add(typeof(B), dataItem => DoSomethingWithB((B)dataItem));

Теперь, когда у вас есть IList полный объектов, вы получаете безопасный для вызова метод для вызова:

var methodToInvoke = methodsToInvokeByType[typeRetrievedFromDictionary];

Затем сделайте это:

foreach(object itemInList in list) // this is your deserialized list cast as IList
{
    methodToInvoke(itemInList);
}

Поэтому, если тип A, вы будете вызывать

DoSomethingWithA ((A) itemInList)

Это не красиво. Соединение между кодом, использующим объекты, и типом и кодом типа safe-generi c может быть грязным. Но в конечном итоге цель состоит в том, что какими бы ни были эти окончательные методы - DoSomethingWithA, DoSomethingWithB и др. c., По крайней мере, они являются типобезопасными.


Вы можете упростить еще немного:

Создать класс, который десериализует список и передает его методу для обработки, а также интерфейсу:

public interface IXmlFileProcessor
{
    void Process(byte[] xmlFile);
}

public class XmlFileProcessor<T> : IXmlFileProcessor
{
    private readonly Action<T> _doSomething;

    public XmlFileProcessor(Action<T> doSomething)
    {
        _doSomething = doSomething;
    }

    public void Process(byte[] xmlFile) // or string or whatever
    {
        // deserialize into a List<T>
        foreach (T item in deserializedList)
            _doSomething(item);
    }
}

Затем создайте Dictionary<Type, IXmlFileProcessor> и заполните его:

fileProcessors.Add(typeof(A), new XmlFileProcessor<A>(SomeClass.DoSomethingWithA));
fileProcessors.Add(typeof(B), new XmlFileProcessor<B>(SomeClass.DoSomethingWithB));

Этот подход (внедрение Action) предназначен для того, чтобы сохранить метод "что-то сделать" отделенным от класса, отвечающего за десериализацию. DoSomething также может быть обобщенным c методом в XmlFileProcessor<T>. Есть разные способы составить эти классы и добавить их в этот словарь. Но в любом случае, определив тип, вы просто извлекаете правильный процессор c, определяющий тип, из словаря, передаете в него свой файл, и он делает все остальное.

Этот подход соединяет общий / не -generi c пробел путем создания класса - XmlFileProcessor<T> - generi c, но с реализацией интерфейса не-generi c. Он работает до тех пор, пока вы делаете шаги (используя словарь), чтобы убедиться, что вы выбираете правильную реализацию для любого типа, который вы десериализуете.

...