接口定义中为什么需要参数名?在实现过程中,是否可以选择新的参数名?

33

不确定这是一个愚蠢的问题,但我刚刚注意到了这个:

public interface IActivityDao : IDao<Activity>
{
    IList<Activity> GetAllSinceSequence(long sequence, int count);
}

public class ActivityDao : AbstractNHibernateDao<Core.Domain.Activity>, IActivityDao
{        
    public IList<Activity> GetAllSinceSequence(long sequence, int maxRecords)
    {

    }
}

在我的实现中,我将第二个参数称为“maxRecords”,但是在接口中,它被定义为“count”。编译器仍然认为接口已经实现了,这很好,但可能会导致一些歧义。显然,我应该重命名其中一个参数以匹配另一个。

在进行重命名之前,我稍微尝试了一下,并注意到了一些有趣的东西。我不允许声明我的接口为:

public interface IActivityDao : IDao<Activity>
{
    IList<Activity> GetAllSinceSequence(long, int);
}

这只是编译器在保护C#语义方面过于谨慎吗?除了使代码更易读之外,接口方法中的参数名称有什么作用?如果不强制实现,我觉得这会导致歧义。

6个回答

53

接口声明需要参数名称以便于实现和参考。如果有人在使用你的接口,方法参数的名称应该具有自说明性,以便于接口消费者了解需要传递给该方法的内容(例如,在通过IntelliSense查看方法说明时)。

当然,实现接口时,你可以任意命名参数。


你应该得到更多的赞。你的回答是迄今为止最好的。你相当简洁地回答了OP的问题。 - Enigmativity
3
好的。回答虽然清晰,但实际上并没有回应我发帖中的最后一句话。为什么在实现过程中不能保证参数名称相同?如果唯一的意图是自文档化和清晰明确,那为什么不将这种清晰度一直延伸到实现层面呢?我不希望无意中将我的方法宣传成接受“count”,当我实际上期望的是“maxResults”。 - Sean Anderson
3
因为您可能希望在实现级别上拥有更具体的自我文档化。假设您创建了一个名为IBinaryMathOperation的接口,并定义了一个方法Execute(int operand1, int operand2)。然后,您实现了一个名为AddOperation的类来实现该接口。在add方法中,“操作数”更具体地称为“加数”,因此您可能希望在Execute()方法的具体实现中使用这个术语。 - KeithS
还有一个原因;假设您实现了三个类,然后注意到它们都具有相同名称和类似目的的方法,并决定“提取”接口。哪种重构更容易:定义具有该方法的接口并将每个类声明为实现它,必要时更改函数名称,还是定义接口,将类定义为实现它,更改方法名称以及所有参数? - KeithS
1
@KeithS,我投票支持选项3)--使用ReSharper并点击“提取接口” ;) - Kirk Woll
这会从一个类中提取参数名称;然后将接口应用于其余的类(但您不必匹配参数名称)。 - KeithS

11

历史。这可以追溯到 .NET 的早期,当时 COM 统治着世界。能够与 COM 进行互操作当时非常重要,没有人会放弃一切来采用全新的编程风格。

这使得 COM 互操作在 .NET 中得到了强力支持。除了需要为接口方法指定命名参数之外,类型库也需要。

有趣的边角案例永远是 C++/CLI 语言。它采用了许多 C++ 语法规则,包括在声明中省略参数名称的能力。换句话说,这是合法的:

    public interface class IFoo
    {
        void bar(int, long, double);
    };

类型库导出程序会生成以下声明:

    HRESULT bar(
                    [in] long p1, 
                    [in] long p2, 
                    [in] double p3);

如果您在 C# 类中实现 IntelliSense 自动生成的接口,那么结果会非常相似:

class FooImpl : cpptemp36.IFoo {
    public void foo(int __p1, int __p2, double __p3) {
        throw new NotImplementedException();
    }
}

这样做没有任何人会开心。


我只是想说,我在2023年十一年后非常感激并喜欢这个回答(尤其是那句富有洞察力的结尾)。 - undefined

3
在代码质量的新世界中,实现级别的参数名称必须与接口级别相同。在 SonarQube 中,有一条规则称为“参数名称应与基本声明和其他部分定义匹配”,并将其称为“不合规代码”。
interface IFoo
{
  void Bar(int i);
}

class Foo : IFoo
{
  void Bar(int z) // Noncompliant, parameter name should be i
  {
  }
}

有趣的是,它指的是这份文档,但未涵盖interfacehttps://wiki.sei.cmu.edu/confluence/display/c/DCL40-C.+Do+not+create+incompatible+declarations+of+the+same+function+or+object 我个人喜欢编写以下形式的代码:
public interface IExtractor<TInput, TOutput>{
    TOutput Extract(TInput input);
}

public class CustomerExtractor : IExtractor<Order, Customer>
{
    public Customer Extract(Order order)
    {
        // ...
    }
}

但是这个工具强制我使用以下声明,并将其标记为关键问题:
    public Customer Extract(Order input)
    {
        // ...
    }

input在这种情况下的含义与order不同。

为了消除噪音,

  • 如果没有使用DI,请使用静态实用程序类(Map、Extract、Combine等)来处理此类类
  • 如果使用了DI,请通过添加辅助变量来欺骗工具
    public Customer Extract(Order input)
    {
       var order = input;

       // use order from here on
    }

刚刚我自己也被Sonar标记了,不确定是否同意将其视为关键问题(更多信息我会这么说),基本上出于您所说的同样原因;它使我能够在实现中赋予变量更多的含义(尤其是在具有泛型的接口的情况下)。虽然我想答案是添加代码注释以提供额外的清晰度并保持变量名称相同。 - d219
现在已经完成了上述工作,但我仍然认为,在接口中使用通用术语(并且合适的情况下)时,参数名称相同时会使类中的代码不够清晰。 - d219

2

让我问一下,除了.NET框架之外,还有任何地方可以定义没有参数名称的方法签名吗?

归根结底,所有事情都是可能的,但大多数事情都有其存在的原因。在这种情况下,我想这是框架和编译器设计的限制,但这真的很重要吗?

毕竟,您正在定义一个用于使用的契约,人们期望它们在那里。


1

许多编程语言,如C#和VB,支持方法的命名参数和可选参数。如果接口中没有参数名称,则无法使用命名和可选参数。给参数命名还有助于读者理解接口的意图和功能。


1
我想这是由于C#中的“命名参数”功能。也就是说,您需要能够按名称指定参数,而不仅仅是按默认顺序:
IActivityDao dao;
dao.GetAllSinceSequence(count: 1, sequence: 2);

当然,如果对象被强制转换为您的实例,则参数名称将不同。

var concreteDao = (ActivityDao) dao;
concreteDao.GetAllSinceSequence(maxRecords: 1, sequence: 2);

4
除了该特点比大多数接口晚了十年左右之外,其他都一样。 - Kirk Woll

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