我如何重构当前使用字典的 C# 代码以减少冗余并提高类型安全性?

How can I refactor this C# code currently using Dictionarys to have even less redundancy and be more typesafe?

由于业务决策高于我的薪水等级,我需要解析和合并多个 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);

第一个问题:有没有办法让 return 更安全?也许使用参数化类型?

问题的第二部分实际上是在消耗这个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() 中或调用每个 methods.c

时转换为正确的类型,这没关系

首先,不在字典中存储 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)

不太好看。使用对象和类型的代码与类型安全的通用代码之间的桥接可能会很混乱。但最终的目标是,无论这些最终方法是什么——DoSomethingWithADoSomethingWithB 等,至少它们是类型安全的。


你可以再简化一些:

创建一个 class 反序列化列表并将其传递给处理方法和接口:

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)旨在使 "do something" 方法与负责反序列化的 class 分离。 DoSomething 也可以是 XmlFileProcessor<T> 中的泛型方法。有不同的方法来组合这些 classes 并将它们添加到该词典中。但无论哪种方式,确定了类型后,您只需从字典中检索正确的特定于类型的处理器,将您的文件传递给它,剩下的就交给它了。

该方法通过使 class - XmlFileProcessor<T> - 通用但实现非通用接口来弥合 generic/non-generic 差距。只要您采取措施(使用字典)确保您为要反序列化的任何类型选择正确的实现,它就会起作用。