【问题标题】:Method overloading in C# behaving unexpectedly when using params keyword使用 params 关键字时 C# 中的方法重载行为异常
【发布时间】:2013-06-05 16:48:03
【问题描述】:

好的,回到基础。我想知道如何正确重载带有params 参数的方法。

这是我的场景。我从我的常规方法开始:

public void MyMethod(MyObject mo)
{
    // method body
}

我为它创建了一个看起来像这样的重载:

public void MyMethod(MyObject mo, params string[] fields)
{
    // new method body

    MyMethod(mo);
}

明显的意图是MyMethod(new MyObject()); 执行原始方法,MyMethod(new MyObject(), "field0"/*, etc...*/); 执行重载方法。但我发现情况并非如此。

实际发生的是MyMethod(new MyObject());执行了重载的方法!

我不明白。在这种情况下,我将如何执行原始方法?

用实际代码更新

好的,下面是产生所描述行为的实际代码。

Class1Base.cs:

public class Class1Base
{
    public virtual void MyMethod(MyObject ob)
    {
        Console.WriteLine("Called Class1Base");
    }
}

Class1.cs:

public class Class1 : Class1Base
{
    public override void MyMethod(MyObject ob)
    {
        Console.WriteLine("called overridden method");
    }

    public void MyMethod(MyObject ob, params string[] fields)
    {
        Console.WriteLine("called OVERLOADED method");
    }
}

MyObject.cs:

public class MyObject
{
    public int Id { get; set; }
    public string Description { get; set; }
}

然后,当我以这种方式执行这段代码时:

var myClass = new Class1();
var myObject = new MyObject();
myClass.MyMethod(myObject);
myClass.MyMethod(null);
myClass.MyMethod(null, "string");

控制台显示:

called OVERLOADED method
called OVERLOADED method
called OVERLOADED method

我希望它会显示:

called overridden method
called overridden method
called OVERLOADED method

为什么不呢?

【问题讨论】:

  • 是否可以完全删除旧的重载并将其功能移至新的重载?如您所见,您可以为 params 方法提供 0 值,它只会为参数创建一个大小为零的数组。
  • 我想这可能是一种选择,但对于更简洁、更可测试的代码,我更愿意使用重载。听起来好像没有办法使用 params 关键字来完成这种类型的重载。
  • 很好奇。使用“重载”实际上是有道理的,实际上它更像是一种“隐藏”原始文件的方法。我想知道您如何调用原始方法。编译器会对此发出警告吗?
  • @DonBoitnott - 没有编译器警告。
  • 我很确定规范说 params 方法应该失去重载决议,如果它只适用于它的扩展形式。你确定这是你看到的行为吗?你能告诉我们呼叫站点吗?

标签: c# overloading params-keyword


【解决方案1】:

我不认为你在告诉我们整个故事。 C# 5 规范的第 7.3.5.2 节(标题为“更好的函数成员”)部分说明:

• 否则,如果 MP 以其正常形式适用并且 MQ 具有 params 数组并且仅适用于其扩展形式,则 MP 是 比 MQ 好。

这里似乎就是这种情况,因为params 版本需要“扩展”为长度为零的数组。事实上,在本地尝试您的代码会产生调用非params 版本的预期结果。

更新:响应您的编辑,现在答案很明确:您正在调用来自Class1 的方法,这意味着在执行重载解析时,标记为override 的方法不是最初考虑。并且由于一个非覆盖的方法是适用的(尽管它是扩展的形式),这就是选择的方法。

具体来说,第 7.6.5.1 节部分内容如下:

• 候选方法集减少为仅包含来自 派生最多的类型:对于集合中的每个方法 C.F,其中 C 是 声明方法 F 的类型,在基中声明的所有方法 从集合中移除 C 的类型。

基类MyMethod()被排除在候选集中,因此不会被算法选中。


这种行为背后的确切原因是为了避免“脆弱的基类”问题的表现。假设我们有以下类层次结构:

class A
{
}

class B : A
{
    public void MyMethod(object o) { }
}

以及以下呼叫站点:

new B().MyMethod("a string");

这显然会解析为采用objectMyMethod()。但是现在假设A 的创建者(也许他在另一个团队工作)决定A 也应该有一个MyMethod()。所以他们改变了他们的班级:

class A
{
    public void MyMethod(string s);
}

现在想象一下,如果我们不从基类型中排除方法会发生什么。您最初解析为B.MyMethod() 的调用突然会解析为A.MyMethod()(因为string 是更好的匹配。)C# 的设计者不想让完全不同团队中的另一个人默默地改变代码的含义。

有关脆弱的基类问题的更多信息,Eric Lippert's (old) blog 有许多关于该主题的帖子。随便搜索一下。

【讨论】:

  • 我在这里得到了相同的结果。一切都按预期工作。
  • 嗯。我创建了一个新的示例项目来重新创建错误,但我无法复制。我无法发布以这种方式运行的代码。我将努力重新创建行为并发布。
  • 请查看我的问题,其中包含重复描述行为的更新。
  • 感谢您如此详细的解释。但是,我不明白这应该如何发挥作用。例如,如果我将public void MyMethod(MyObject ob, params string[] fields) 更改为public void MyMethod(MyObject ob, string field),那么它会按预期工作。会不会是 params 特有的东西导致了这种情况?
  • @quakkels 你有两种方法,理论上这两种方法都适用。但是,最初在基类中声明的方法最初会被忽略,因此只剩下一个适用的方法(派生类中带有 params 的方法。)如果将派生类版本更改为 MyMethod(MyObject ob, string field),则 它不再适用,因此再次考虑基类版本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 1970-01-01
相关资源
最近更新 更多