【问题标题】:Really impossible to use return type overloading?真的不可能使用返回类型重载吗?
【发布时间】:2009-09-26 12:45:00
【问题描述】:

我用两种方法在 MSIL 中制作了一个小 DLL:

float AddNumbers(int, int)
int AddNumbers(int, int)

您可能知道,MSIL 允许您创建具有相同参数的方法,只要您有不同类型的返回类型(称为返回类型重载)。现在,当我尝试从 c# 中使用它时,正如我所期待的那样,它触发了一个错误:

float f = ILasm1.MainClass.AddNumbers(1, 2);

错误是:

以下方法或属性之间的调用不明确: 'ILasm1.MainClass.AddNumbers(int, int)' 和 'ILasm1.MainClass.AddNumbers(int, int)'

c#真的无法区分不同的返回类型吗?我知道我不能对只有不同返回类型的方法进行编程,但我总是假设它会知道如何处理它。

【问题讨论】:

标签: c# .net


【解决方案1】:

ECMA-334 C# 第 8.7.3 节

方法的签名包括 方法的名称和编号, 修饰符及其形式的类型 参数。 方法的签名 不包括返回类型。

您可以使用通用方法:

T AddNumbers<T>(int a, int b)
{
   if (typeof(T) == typeof(int) || typeof(T) == typeof(float))
   {
      return (T)Convert.ChangeType(a + b, typeof(T));
   }

   throw new NotSupportedException();
}

【讨论】:

  • 使用泛型方法是一个很好的方法。
【解决方案2】:

正如其他人所说,没有 C# 不支持这一点。事实上,IL 支持这一点的原因是因为您必须明确返回类型,就像参数一样。例如,在 IL 中你会说

ldarg.0
ldarg.1
call int AddNumbers(int, int)

IL 实际上并没有方法重载的概念:就 IL 而言,float AddNumbers(int, int)int AddNumbers(int, int) 没有任何关系。你必须提前告诉 IL 编译器一切,它从不试图推断你的意图(就像 C# 等高级语言一样)。

请注意,大多数 .NET 语言和 C# 对返回类型重载有一个例外:转换运算符。所以

public static explicit operator B(A a);
public static explicit operator C(A a);

编译成

public static B op_Explicit(A a);
public static C op_Explicit(A a);

因为这是一个特殊的极端情况,必须支持原始类型(例如 int -> bool)和引用类型转换(否则你会得到一种非常迂腐的语言),所以会处理,但不是一个方法重载的案例。

【讨论】:

    【解决方案3】:

    是的,这在 C# 中确实是不可能的,我知道 C++ 也不允许这样做,这与语句的解释方式有关:

    double x = AddNumbers(1, 2);
    

    这里的规则是赋值是右关联的,这意味着首先完全评估右边的表达式,然后才考虑赋值,在必要时应用隐式转换。

    编译器无法确定哪个版本最合适。使用一些任意规则只会招致难以发现的错误。

    与这个简单的语句有关:

    double y = 5 / 2;  // y = 2.0
    

    【讨论】:

    • 双倍的好例子。
    • 所以实际上没有办法指定在 C# 中使用返回类型重载时要调用哪个方法?
    • @thecoop:不,这就是为什么你不能通过返回类型重载。
    • 因此,具有返回类型重载(例如,从直接 IL 编译)的 IL 程序集无法从 C# 中使用:/
    • @thecoop:我认为不会。并且很确定这些不“符合 CLS”。
    【解决方案4】:

    问题是有一个从 int 到 float 的自动转换,所以它真的不知道你想要什么。您是打算调用接受两个整数并返回一个整数的方法,然后将其转换为浮点数,还是打算调用接受两个整数并返回一个浮点数的方法?最好有一个编译器错误,而不是做出错误的选择,直到你的应用程序崩溃才让你知道。

    【讨论】:

    • @devoured 不,它不能。 MSIL 不会猜到您指的是哪一个。你指定你的意思。 c# 在这种情况下没有指定机制。
    【解决方案5】:

    MSIL 能够在同一个程序集中保存这两种方法与编译器确定调用哪个方法无关。

    协变返回类型已经讨论了很多年,它们在 Java 中是部分允许的,C++ 有一个特殊情况,C# 设计者有added some minor changes to 4.0

    对于您的玩具问题,有简单的典型解决方案。例如,您的float AddNumbers(int, int) 可能总是与(float) AddNumbers(int, int) 相同,在这种情况下不需要第二个函数。通常这种情况是用泛型处理的:&lt;T&gt; AddNumbers(&lt;T&gt; n1, &lt;T&gt; n2),所以你最终会得到float AddNumbers(float, float)int AddNumbers(int, int)

    现实生活中的场景更有可能是您想要返回不同的表示,但您不想重命名方法。在继承/覆盖用例中,也可以解决这个somewhat with generics

    在少数情况下,我想按照您的意愿行事,实际上最好更恰当地命名方法,因为从长远来看,它更具可读性和可维护性。

    这也已经讨论过here

    MSIL/C# 中 http://blogs.msdn.com/abhinaba/archive/2005/10/07/478221.aspx 的情况很特殊,因为它们是显式转换运算符。

    【讨论】:

    • 您能否突出显示链接页面的哪一点“暗示它可能会出现”?
    • 更正了仅提及 4.0 更改的措辞。
    • “协变返回类型”与“返回类型重载”不同。
    【解决方案6】:

    不,没有办法做到这一点。事实上,除了在带有模板的 C++ 中,没有任何语言支持它。这显然太危险了。再说一次,如果你写怎么办

    var a = AddNumbers(1, 1);
    

    a 应该是什么类型?

    或者如果你这样称呼它怎么办

     double a = AddNumbers(1, 1);
    

    甚至

     AddNumbers(1, 1);
    

    它应该调用什么版本?

    请记住,关于如何将一种类型隐式转换为另一种类型有相当复杂的规则。让我们看一下无法编译的简单程序

    class Program
    {
        static int parse(int a) { return a; }
        static float parse(float a) { return a; }
    
    
        static void Main(string[] args)
        {
            double a = parse(1.0);
        }
    }
    

    如果你尝试编译它,编译器会给你一个错误

    error C2668: 'parse' : ambiguous call to overloaded function
    could be 'float parse(float)'
    

    因为 1.0 有类型 double 并且编译器真的不知道在 intfloat 之间选择什么类型,所以它要求你给它一个提示。因此,您可以在调用函数之前继续转换参数。

    但是如果是函数重载的返回类型,那你怎么做呢?根本没有办法。

    【讨论】:

    • 在第一种情况下,它会发出警告说“x 返回类型假定”。第二种情况,将返回浮动版本,第三种情况它会像第一种情况一样工作。
    • 那么重点是:如果 MSIL 可以做到,为什么 c# 不能做到?
    • 不知道为什么 MSIL 会这样做。也许你可以选择你想要的功能。但是带有警告的想法是错误的。想象一下,你得到了两个副作用略有不同的函数。程序运行良好,直到您删除其中一个功能或添加第三个功能。然后一切都神奇地停止工作,你完全失去了试图找出发生的事情,因为你的系统中到处都是错误,这似乎与上一次更改完全无关。这就是为什么 C++ 被认为是危险的,这就是为什么当编译器无法在这里做出决定时,没有人会发出警告。
    • Henk 明确表示“这里的规则是赋值是右关联的,这意味着首先完全评估右边的表达式,然后才考虑赋值,在必要时应用隐式转换。” .所以这就是为什么...
    【解决方案7】:

    使用指针将返回类型作为参数传递怎么样?

    void AddNumbers(int a, int b, float *ret){
      *ret = (float)(a + b);
    }
    
    void AddNumbers(int a, int b, int *ret){
      *ret = (int)(a + b);
    }
    

    调用它会变成这样:

    int a;
    float b;
    AddNumbers(1, 2, &a);
    AddNumbers(1, 2, &b);
    

    【讨论】:

    • 是的,这是个好主意,但不是我想要的。
    • 这是对的,但是如上所述,你不能做你想做的事。这是一个解决方法。它解决了同样的问题,但方式不同。除非有你没有提到的其他要求。如果有,让我们听听,看看我们是否可以改进这个解决方案;)
    • 没有要求。我只是想知道这是否可能。
    • dharga,使用out 参数可以获得完全相同的分辨率,不需要[不安全] 指针。
    • dharga,编译器不会让你使用没有 [unsafe] 属性的 C# 指针。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多