将选项参数添加到c#中的库而不创建重大更改

时间:2017-05-22 03:46:10

标签: c#

我有一个nuget包,其中有一个方法有可选的args(v1.0)

public void MyMethod(int a = 0){}

我后来用另一个可选的arg创建了我的包的新版本(认为它不是一个重大变化)(v1.1)

public void MyMethod(int a = 0, int b = 1){}

这个包由另一个引用v1.0的nuget包使用我同时拥有v1.1和我项目中包含的另一个nuget包。这意味着在绑定级别Im使用v1.1,使用v1.0的包重定向到1.1 dll。

由于以下原因,这会导致缺少方法异常: https://stackoverflow.com/a/9884700/1070291

我想修复我的库,这样任何一个签名都会起作用,我首先想到的是:

public void MyMethod(int a = 0, int b = 1){}
public void MyMethod(int a = 0){ MyMethod(a,1); }

然而,当它在其他地方使用时会导致模糊的方法。

我想知道是否有某种方法可以填充旧方法以实现向后兼容,而不会产生任何模糊的东西。

我几乎想用旧的签名来指示编译器将其包含在程序集中,但不将任何新方法链接到它。

4 个答案:

答案 0 :(得分:6)

提供一个明确没有参数的方法。例如:

public void MyMethod(int a = 0, int b = 1) { }
public void MyMethod(int a = 0) { MyMethod(a, 1); }
public void MyMethod() { MyMethod(0, 1); }

这消除了没有传递给MyMethod的参数的歧义。

扩展更多参数意味着您可以编写如下内容:

public void MyMethod(int a, int b, int c) { }
public void MyMethod(int a, int b) { MyMethod(a, b, 2); }
public void MyMethod(int a) { MyMethod(a, 1); }
public void MyMethod() { MyMethod(0); }

请注意,我们可以通过显式实现每个重载来完全删除可选参数。我们确实失去了一些关于默认参数的intellisense帮助,但这可以用代码注释替代。

答案 1 :(得分:2)

我会定义1个非可选参数,使其不明确。

public void MyMethod(int a, int b){} // first new method: this will be your main functioning method
public void MyMethod(int a = 0){ MyMethod(a,1); } // second old method: calls the main method above

这里不应该含糊不清,因为:

  • 0参数将调用第二个旧方法,该方法将调用第一个新方法
  • 1参数将调用第二个旧方法,该方法将调用第一个新方法
  • 2个参数将调用第一个新方法

简而言之,每种方法都针对第一种新方法。

答案 2 :(得分:2)

我认为尝试使用其他带有默认参数的重载来解决默认参数引入的问题只是为了预防将来遇到更多麻烦......

不间断的更改:我要做的是添加一个名为的新方法,并按原样保留旧方法。 界面的呼叫者不会受此更改的影响,新用户将拥有全功能的新方法。

public void MyMethod(int a = 0) {
    MyBetterMethod(a, 1);
}

public void MyBetterMethod(int a, int b) { }

从旧语法迁移:标记旧方法[Obsolete](并在将来的版本中将其删除)。不要忘记建议在警告消息中使用 right 方法。

将其保留为少数版本的警告:

[Obsolete("This method is obsolete, use MyBetterMethod() instead.")]
public void MyMethod(int a = 0){}

使用您的下一个主要版本会使其成为编译时错误:

[Obsolete("This method is obsolete, use MyBetterMethod() instead.", true)]
public void MyMethod(int a = 0){}

稍后您可以考虑明确删除该代码并破坏二进制兼容性。生命周期在这个Eric Lippert's SE answer中有更好的描述(请注意,对于简单部署,如果您不需要保持二进制兼容性,他建议在弃用变为编译时错误时立即删除代码。)

赞成使用新语法隐藏过时的方法,以尽量减少新用户调用它而不是新用户的机会:

[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is obsolete, use MyBetterMethod() instead.")]
public void MyMethod(int a = 0){}

从中学习:不要使用可选参数,这只是您将遇到的问题之一(当然有正确的用例,但IMO是个例外,不是规则)。

一般情况下,在编写图书馆时,您的公共界面采用不同的方法,请务必小心您的班级合同(读作:快速修复是您不想拥有的遗产

当你的方法参数太多而你想让调用者的生活更轻松时,你可能会遇到设计问题,而且在极少数情况下确实需要默认值,那么你应该强烈考虑使用重载版本。也就是说,我无法判断,因为我没有看到你的真实代码,新方法有可能做出不同的事情,或者做得太多。

当许多参数不可避免时,您应该考虑将它们全部分组到一个单独的类中,该类将是该方法的唯一参数。将来任何时候都可以为该类添加更多属性,并且不会破坏兼容性。例如,请参阅Process.Start()及其ProcessStartInfo。在这种情况下,您甚至不需要重命名新方法:

public sealed class MyMethodInfo {
    public int A { get; set; } = 0;
    public int B { get; set; } = 1;
}

public void MyMethod(MyMethodInfo info) {
}

[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("This method is obsolete, use MyMethod(MyMethodInfo) instead.")]
public void MyMethod(int a = 0) {
    MyMethod(new MyMethodInfo { A = a });
}

一个重要的注意事项:默认值是您的接口合同的重要部分,使用它们 cum grano salis ,并且只有在它们真正有意义时,或强制调用者指定值。总是

答案 3 :(得分:-1)

为了完整起见,我最终解决了这个问题。

public void MyMethod(int a = 0){ MyMethod(1,a); }
public void MyMethod(int b, int a = 0){}
  • 恢复原始方法签名
  • 在新的重载中添加了新参数作为reqired参数(在args列表中的现有选项之前强制它)
  • 使用旧签名中的默认值调用新

净效应是一个可选的'参数用重载完成。

这是有效的,因为现在只能使用new参数调用new重载,使其与旧实现不一致。

有些事我不喜欢这样做

  • 参数顺序不是逻辑
  • 如果你重复这个过程,事情变得更加混乱,你必须处理排列

Nuget规则和可选

继承我的psudo规则,以帮助将来更好地做到这一点。

  • 如果添加参数(无论是否有选项),必须在新的重载中完成
  • 新的参数不能是可选的(永远)
  • 如果您需要添加一个可选项(例如[CallerMemberName]的内容),您必须使用重载来更改签名(通过添加另一个非选项参数,更改方法名称等)

重要的是要注意这只适用于预编译的库(如nuget),如果你的项目是引用你不会遇到这个问题的东西。

感谢所有回答并帮助我思考的人