接口中没有默认值的可选参数

7

我惊讶地发现,C# 在接口的可选方法参数中使用的是值,而不是实现此接口的类。例如:

using System;
                
public class Program
{
    private static IMyInterface instance;
    
    public static void Main()
    {
        instance = new MyClass();
        
        instance.PrintOpt();
        ((MyClass)instance).PrintOpt();
    }
}

public interface IMyInterface
{
    void PrintOpt(bool opt = false);
}

public class MyClass : IMyInterface
{
    public void PrintOpt(bool opt = true) 
    {
        Console.WriteLine($"Value of optional argument is {opt}");
    }
}

生成输出:

可选参数值为假

可选参数值为真

我的问题是:是否可能在接口中定义一个无默认值或“可重写”的可选参数,以便在使用实现该接口的类定义的可选参数值时,对保存在接口类型变量中的实例调用方法时使用该值?


为什么这让你感到惊讶呢?你将实例变量定义为IMyInterface - 你为什么期望它使用除接口定义之外的任何东西呢? - BoltClock
2
Eric Lippert在这个SO答案中给出了一个很好的相关解释。 - Steven Rands
4
我很惊讶默认值不是方法签名的一部分。换句话说,接口规定了一个带有布尔参数默认为FALSE的方法,我很惊讶类没有被要求实现带有布尔参数默认为FALSE的方法。 - Joe
1个回答

10

如果您理解可选参数在内部的处理方式,那么这并不奇怪:它们在编译期间被内联。

换句话说,在调用方法的位置,任何可选参数都是由编译器传递的 - 如果您调用一个接口方法,编译器不知道存在具有不同可选参数的实现。可以通过以下代码最好看出区别:

IMyInterface interface = new MyClass();
MyClass theClass = (MyClass)interface;

interface.PrintOpt(); // false
theClass.PrintOpt(); // true

这被编译为以下内容(翻译回C#):

interface.PrintOpt(false);
theClass.PrintOpt(true);

IL代码中的“默认”参数不再是“默认” - 它们只是另一个明确传递的参数。

如果你想使用可重写的可选参数,可以使用方法重载。或者更好的方法是使用没有实际意义的默认值(例如nulldefault(int?)),并在方法内部进行任何默认替换。这符合最初为C#添加可选参数的原因 - VB风格的COM接口通常具有几十个参数的方法,其中几乎所有参数都是可选的。直到现在,当你想调用这样的方法时,你必须执行类似于下面的操作:

comInterface.MyMethod(TheActualArgumentICareAbout, Type.Missing, Type.Missing, 
                      Type.Missing, Type.Missing, ...);

现在你只需要这样做

comInterface.MyMethod(argument, anotherSuperUseful: true);

这个区别很重要 - 这也意味着您不应该更改任何公共方法的默认参数。在没有重新编译的情况下,使用您的库的任何人仍将使用旧的默认值。这类似于如何处理const值或枚举。如果您使用null作为默认值,实际默认值将位于方法本身内部,即使调用者没有重新编译,所有调用者也将“看到”正确的默认值(与使用public static readonly字段而不是const或使用具有属性的类而不是枚举类似)。


1
实际上,我想说可选参数并不存在,只有带有默认值的参数。 - Leonardo Spina

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