接口实现和可选参数

18

考虑这个界面:

interface ILogger
{
    void Store(string payload);
}

而这是ILogger的类实现:

class Logger : ILogger
{
    void Store(string payload, bool swallowException = true)
    {
        ...
    }
}

我预计编译器会将swallowException识别为可选参数,从而满足接口的要求。然而,实际情况是编译器抱怨Logger没有实现接口成员Store

我尝试过另一个有趣的事情,就是显式地实现接口,像这样:

class Logger : ILogger
{
    void ILogger.Store(string payload, bool swallowException = true)
    {
        ...
    }
}
编译器发出警告:“指定给参数 'swallowException' 的默认值将无效,因为它适用于在不允许可选参数的情况下使用的成员。” 这似乎表明可选参数与显式接口定义不兼容,但是为什么呢?
我可以通过重载Store的两个单独函数定义来解决这个问题(即可选参数存在之前的做法)。 但是,我喜欢可选参数的语法清晰性,并希望它按我所期望的方式正常工作。
我明白可能有一个合理的(历史或其他)解释说明为什么事情是这样的,但是我似乎无法弄清楚。

在我的“谷歌搜索”中,我发现了一篇由Eric Lippert撰写的启发性博客文章,似乎为我的问题提供了答案,但是对我来说仍不太清楚。http://blogs.msdn.com/b/ericlippert/archive/2011/05/09/optional-argument-corner-cases-part-one.aspx - James Jones
1个回答

25

因为C#中的可选参数只是语法糖。

在您的情况中,该方法的定义为

void Store(string payload, bool swallowException)

而不是

void Store(string payload)

显然这与接口不匹配。

默认参数的工作方式是编译器将默认值注入到方法的调用中。因此,如果您执行Store(payload),编译器实际上会发出Store(payload, true)。这对于理解默认参数非常重要 - 它在调用者的编译时完成。因此,如果您在被调用的方法中更改了默认参数而没有重新编译调用方,则调用方仍将使用旧的默认参数。

这也解释了您收到的警告 - 由于默认值是由编译器显式传递的,并且您无法调用接口的显式实现而不进行接口转换,因此您永远不会有机会使用默认值。

实际上,您根本不想使用默认参数。只需像这样定义两个方法:

void Store(string payload, bool swallowException)
{
  // Do your job
}

void Store(string payload)
{
  Store(payload, true);
}

这样做可以避免上述两个问题 - 接口合约得到满足,并且默认参数现在是调用方的一部分,而不是调用者的一部分。

个人而言,在公共API方法中我根本不使用可选参数 - 当您决定在某些时候更改它们时,它们很容易引起麻烦。除非您能确保它们将永远保持不变,否则不要使用它们。相同的适用于constenum - 这两者也是在编译时确定,而不是运行时。

请记住,包括默认参数的原因是允许您不传递某些参数。对于诸如COM API调用(否则需要您将所有不想传递的参数作为Type.Missing传递)或null值之类的事物来说,这是有意义的。即使使用false,当有人决定更好的默认值是true时,这也只会带来麻烦 - 突然间,一些调用者正在使用true,而一些调用者正在使用false,尽管所有人都认为他们正在使用“默认值”。对于您的情况,我会使用bool?,默认值为null(或者default(bool?),无论您喜欢哪个)。在方法代码本身中,您可以轻松处理适当的默认值 - 例如,通过执行swallowException.GetValueOrDefault(true)


我现在认为我更好地理解了可选参数的机制。我明白你所说的显式接口定义需要完全匹配的意思。我应该意识到在这里使用可选参数是没有意义的,我的错误。 - James Jones
1
除了显式的情况,编译器不能替我发出两个函数定义吗?换句话说,让编译器做你建议我手动完成的相同操作?我认为这将消除您所描述的问题,即调用者使用过时的默认值进行编译。我是否有什么逻辑上的原因没有考虑到而无法正常工作? - James Jones
无论如何,考虑到可选参数的工作方式,我想我肯定会遵循您的建议,不使用公共方法的可选参数...我受到启发了!谢谢。 - James Jones
1
@JamesJones 当然可以。但这不是可选参数的工作方式 :) 我认为他们想避免处理这些方法的重载 - 只有一个重载,具有正确数量(和类型)的参数。如果为每个生成一个重载,可能会与类型上定义的其他方法冲突。而且通常,在您想要进行此类“默认设置”时,还涉及一些(简单的)输入操作,因此它并不那么有用。重要的是 - 命名参数。您可以选择忽略所有默认参数,同时选择要传递的参数。 - Luaan
@JamesJones 命名参数也可能是最有力的论据。再次考虑一下 COM 方法调用,其中您有一个具有 30 个参数的方法(曾经使用过 Excel API 吗?:)),而您只想传递其中的三个参数,其余的则为 Type.Missing。如果默认参数仅确保该方法有 30 个重载,则无法挑选要传递的参数 - 它们总是必须按顺序。 - Luaan
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接