Как я могу реорганизовать этот код C #, который в настоящее время использует Dictionarys, чтобы иметь еще меньше избыточности и быть более безопасным для типов?

Из-за бизнес-решений, которые выше моей зарплаты, мне нужно разобрать и объединить несколько 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 и т. д.) имеет свойство string Id, которое определенно понадобится для всех doStringWithX() методов... если это полезно, я могу создать интерфейс для этого. Ничего страшного, если мне нужно привести к правильному типу в каждом doStringWithX() или при вызове каждого из этих методов.c


person Brian Kessler    schedule 29.04.2020    source источник


Ответы (1)


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

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

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

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

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

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

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)

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


Можно еще упростить:

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

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

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

person Scott Hannen    schedule 29.04.2020
comment
Это выглядит очень многообещающе и кажется именно тем направлением, над которым я работал. Я обязательно попробую это! Ваше здоровье! :-) - person Brian Kessler; 29.04.2020
comment
При дальнейшем осмотре,. похоже, что мой первоначальный вопрос несколько неверен, потому что оказывается, что файлы XML и объекты С# имеют некоторые дополнительные оболочки, усложняющие ситуацию.... Но в приведенном выше определенно была некоторая полезная информация, например, создание списка общих объектов (даже хотя я пошел другим путем, чтобы сделать это). Я воспользуюсь довольно незначительным сомнением, что это сработало бы, если бы моя проблема была такой, как указано, и приму этот ответ. Ваше здоровье! - person Brian Kessler; 30.04.2020