【问题标题】:How to handle multiple identical classes in C#如何在 C# 中处理多个相同的类
【发布时间】:2018-11-02 16:30:34
【问题描述】:

我正在使用具有多个不同“服务”(到目前为止大约 10 个)的客户端 API,每个都作为自己的命名空间导入。他们的标准 API 调用模式的一部分涉及返回一组错误消息:

public class Error {
    public String ErrorMessage {get;set}
    public int errorNumber {get;set}
    ..etc
}

我一直在努力清理和统一我们对这些消息的处理。我尝试制作一个函数来处理它们,例如:

void CheckErrors(List<Error> errors) {
    if(errors != null && errors.Count() > 0) 
        throw new Exception(errors.First().ErrorMessage);
}

(实际功能更复杂,但给出了大致思路)

然而,事实证明,他们的每一项服务都有自己的(相同的)这个 Error 类的定义。在 C++ 中,我可以只对这个函数进行模板化,它可以正常工作,或者在动态语言中它可以正常工作,但在 C# 中,我无法找到一种方法来做到这一点,而无需制作 10 多个相同函数的副本, 每个在 Error 类型上都有不同的命名空间。

在我自己的代码中,我可以为这些类添加一个接口,但由于这不是我的代码,我认为你不能在 C# 中做到这一点?我可以创建一个继承自其中每一个并实现接口的包装器类,但我仍然为每个命名空间/服务遇到重复代码。

有没有更简洁的方法来处理这个问题?

【问题讨论】:

标签: c#


【解决方案1】:

您可以考虑使用反射或dynamic 的后期绑定解决方案。两者都有相同的缺点:您会失去编译时类型安全性,但如果它是一段非常孤立且包含的代码,它应该是可以容忍的:

  • 反思

    void CheckErrors(List<object> errors) {
        if(errors != null && errors.Count() > 0) 
        {
            var firstError = errors.First();
            throw new Exception(
                firstError.GetType()
                          .GetProperty("ErrorMessage")
                          .GetValue(firstError)
                          .ToString()); }
    
  • 动态

    void CheckErrors(List<dynamic> errors) {
        if(errors != null && errors.Count() > 0) 
            throw new Exception(errors.First().ErrorMessage); }
    

【讨论】:

    【解决方案2】:

    请耐心等待...您可能还需要另一个 Error 类,它的属性与它们的 Error 类相同,但您在命名空间中定义。

    您的方法CheckErrors 使用您对Error 的定义。

    最后,您可以使用AutoMapper 在他们的Error 类型和您的类型之间进行映射。这几乎正​​是 AutoMapper 的设计目的。由于您的所有合约都是相同的,因此 AutoMapper 配置应该是微不足道的。当然,您会产生一些映射的运行时开销,但我认为这将导致最干净的静态类型解决方案,因为您无法更改它们的接口。

    AutoMapper 配置 + 用法如下所示:

    //See AutoMapper docs for where to put config, it shouldn't happen on every call
    var config = new MapperConfiguration(cfg => 
    {
        cfg.CreateMap<TheirApi.Error, MyNamespace.MyErrorDefinition>();
    }
    var mapper = config.CreateMapper();
    
    MyErrorDefinition myErrors = mapper.Map<List<MyErrorDefinition>>(listOfTheirErrorObjects);
    CheckErrors(myErrors);
    

    【讨论】:

    • 恕我直言,这太过分了
    • 如果不使用 AutoMapper 编写自己的 simlpe 工厂,我也会这样做
    • 绝对值得考虑 - 这真的取决于 OP 在这里写的内容。也许你真的不想要那种依赖,因为你正在构建一些引入它的东西是一个真正的问题。或者,也许您只需要这些愚蠢的错误类来执行您希望它们执行的操作,而不是编写不是 1 组而是 10 组映射代码 :)。 “AutoMapper 是一个简单的小库,旨在解决一个看似复杂的问题——摆脱将一个对象映射到另一个对象的代码。这种类型的代码写起来相当枯燥乏味,所以为什么不发明一个工具来为我们做呢? "
    • 请记住,如果第三方 Error 类有十个版本,则 OP 必须总共调用 cfg.CreateMap() 十次。但是 AutoMapper 确实消除了逐个属性编写代码的需要,如果有很多属性,这可能会有所帮助。如果只有 ErrorNumber 和消息,那么我不会打扰映射器。
    【解决方案3】:

    另一种方法是使用 lambda:

        void CheckErrors<T>(IEnumerable<T> errors, Func<T,string> getMessage)
        {
            if (errors?.Count() > 0) throw new Exception(getMessage(errors.First()));
        }
    

    然后这样称呼它:

    CheckErrors(errors, (e) => e.ErrorMessage);
    

    【讨论】:

    • 用 lambdas 修复它。
    • 好主意,但我觉得这仍然是到处重复代码
    • 如果您喜欢 automapper 解决方案,那么对于将“他们的”异常映射到“您的”异常的每一行,您只需创建一个函数 CheckError(IEnumerable errors) ( CheckError(errors, (e) = > e.ErrorMessage); }。对于每个“他们的”类型,它是相同的 +1 行,但在这里你可以通过编译器进行类型检查,并且没有额外的依赖。
    【解决方案4】:

    我会定义我自己的 Error 类,它有一个构造函数,可以接受来自供应商的任何错误对象并将其转换。例如:

    public class Error
    {
        public string Message { get; private set; }
        public int ErrorNumber { get; private set; } 
    
        public Error(object vendorError) 
        {
            var t = vendorError.GetType();
            foreach (var source in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
            {
                foreach (var dest in typeof(Error).GetProperties(BindingFlags.Instance | BindingFlags.Public))
                {
                    if (dest.Name != source.Name) continue;
                    if (dest.PropertyType != source.PropertyType) continue;
                    dest.SetValue(this, source.GetValue(vendorError, null));
                }
            }
        }
    }
    

    然后,当您有来自第三方库的错误列表时,您可以使用 LINQ 对其进行转换:

    var myErrorList = vendorErrorList.Select( e => new Error(e) );
    

    现在您可以正常访问属性了。

    See my example on DotNetFiddle

    【讨论】:

    • 很好的现代反射方式。为那个答案+1 :-)
    猜你喜欢
    • 2010-12-14
    • 2015-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-13
    相关资源
    最近更新 更多