在 c# 中将选项参数添加到库中,而无需创建中断性更改

  • 本文关键字:创建 中断 选项 参数 添加 c#
  • 更新时间 :
  • 英文 :


>我有一个nuget包,其中我有一个具有可选参数的方法(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 包。这意味着在绑定级别,使用 v1.1 的 Im 和使用 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); }

但是,当在其他地方使用时,这会导致方法不明确。

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

我几乎想用一些东西标记旧签名,以指示编译器将其包含在程序集中,但不将任何新方法链接到它。

提供一个显式没有参数的方法。例如:

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); }

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

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

非重大更改:我要做的是添加一个具有不同名称的新方法,并保持旧方法不变。界面的调用者不会受到此更改的影响,新用户将拥有功能齐全的新方法。

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

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

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

[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 的 SE 答案中得到了更好的描述(请注意,对于简单的部署,如果您不需要保持二进制兼容性,他建议在弃用成为编译时错误后立即删除代码。

支持使用新语法:隐藏过时的方法,以最大程度地减少新用户调用它而不是新语法的机会:

[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 });
}

一个重要的注意事项:默认值是接口协定的重要组成部分,仅在它们真正有意义时才使用它们,或者强制调用方指定值。总是。

我会定义 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 个参数将调用第一个新方法

简而言之,每个方法都指向第一个新方法。

为了完整起见,这就是我最终解决这个问题的方式。

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

净效应是使用重载完成的"可选"参数。

这是有效的,因为现在只能使用新参数调用新的重载,使其与旧实现毫不含糊。

有些事情我不喜欢不得不这样做

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

Nuget 的规则和可选

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

  • 如果添加参数(无论是否有可选),则必须在新的重载中完成
  • 新参数不能是可选的(永远)
  • 如果您需要添加一个可选(例如带有[CallerMemberName]的东西),则必须使用重载来执行此操作,该重载也会更改符号(通过添加另一个非 optiona 参数,更改方法名称等)

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

感谢所有回答并帮助我思考这个问题的人

最新更新