【问题标题】:Static type-checking of dynamic types not sharing an interface in C#在 C# 中不共享接口的动态类型的静态类型检查
【发布时间】:2018-04-23 21:03:41
【问题描述】:

假设我有一些外部类,它们的方法按约定共享签名,但没有实现接口:

class A {
  public string getError();
}

class B {
  public string getError();
}

如果我想创建一个接受任一类型对象的实用函数,我可以使用dynamic

void printError(dynamic obj) {
  Console.WriteLine("Error: {0}", obj.getError());
}

但是,如果我传递了某个类 C 而没有 getError 方法,这将是一个运行时错误。鉴于我不能(直接)修改AB,有没有办法实现这一点,这样在没有getError 方法的情况下传递类型将是编译时错误,类似于我们'd get with C++ 模板?

【问题讨论】:

  • 你不能只为每种类型设置一个重载吗? printError(A obj)printError(B obj)
  • 另一种选择是编写共享公共接口的包装类。
  • 或者,void printError(Func<string> getErrorFunc) 并像 printError(new A().getError) 一样调用它?当然,如果约定比这个例子更复杂,那会很痛苦。
  • 你也可以查看obj.GetType().GetMethod("getError") != null(虽然不保证没有参数,就是返回字符串)
  • 这些类不能实现接口有什么原因吗?

标签: c# types type-conversion


【解决方案1】:

听起来你有一个 XY 问题。与其弄清楚如何降低代码的类型安全性,不如直接使用重载和重构?

public void PrintError(A obj) 
{
    Console.WriteLine("Error: {0}", obj.getError());
    //maybe some more logic
}

public void PrintError(B obj) 
{
    Console.WriteLine("Error: {0}", obj.getError());
    //maybe some more logic
}

上面的代码解决了你的问题,但它不再是 DRY。这是我们重构的地方:

public void PrintError(A obj) 
{
    PrintError(obj.getError());
}

public void PrintError(B obj) 
{
    PrintError(obj.getError());
}

//This is private, so you can only call PrintError publicly with an A or B instance
private void PrintError(string error)
{
    Console.WriteLine("Error: {0}", error);
    //maybe some more logic
}

现在你有了类型安全,你不会重复自己,也不会以与设计方式相反的方式使用它来“与语言作斗争”。

【讨论】:

    【解决方案2】:

    使用多个原型

    您当然可以定义多个原型,其中每个原型接受不同的类。编译器将自动选择匹配的方法签名。示例:

    public static void PrintError(ExternalClasses.A a)
    {
        Console.WriteLine(a.getError());
    }
    public static void PrintError(ExternalClasses.B b)
    {
        Console.WriteLine(b.getError());
    }
    

    使用单个原型,但带有隐式转换

    另一种方法是定义自己的类并设置隐式转换:

    public class ErrorContainer
    {
        protected string _error = null;
    
        public string getError()
        {
            return _error;
        }
    
        public override string ToString()
        {
            return getError();
        }
    
        static public implicit operator ErrorContainer(ExternalClasses.A a)
        {
            var e = new ErrorContainer();
            e._error = a.getError();
            return e;
        }
        static public implicit operator ErrorContainer(ExternalClasses.B b)
        {
            var e = new ErrorContainer();
            e._error = b.getError();
            return e;
        }
    }
    

    然后你可以定义 PrintError 来接受一个 ErrorContainer:

    public static void PrintError(ErrorContainer e)
    {
        Console.WriteLine(e);
    }
    

    ...允许您“通过”任一类型:

    var a = new ExternalClasses.A();
    PrintError(a);
    
    var b = new ExternalClasses.B();
    PrintError(b);
    

    当然,在幕后,AB 都会转换为 ErrorContainer

    Example on DotNetFiddle

    【讨论】:

    • 感谢示例显示如何通过隐式转换强加排序接口。在这两个答案的字里行间阅读,似乎用样板代码枚举类型是在这里获得静态类型安全的唯一方法,对吗?
    猜你喜欢
    • 2010-11-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-14
    • 2012-01-17
    相关资源
    最近更新 更多