使用三个可选参数与一个可选参数时向后兼容
Backward compatible when using three optional parameters vs one optional parameters
以前我有这样的方法:
public void Test(string a, string b = null, int? c = null, string d = null)
重新设计后,我们决定将所有可选参数分组到一个 class 中,如下所示:
public class TestOptions
{
public TestOptions()
{
}
public string b { get; set; }
public int? c { get; set; }
public string d { get; set; }
}
所以方法内部实现不会改变但是签名会变成:
public void Test(string a, TestOptions options = null)
我的问题是,第一个三个可选参数的方法已经在使用了,如果我现在想添加这个新方法,应该如何通过重载实现向后兼容?
这取决于你关心的是代码兼容性还是二进制兼容性;后者通常是强制性的(为了不破坏编译后的代码),但前者可以更灵活一些。
你不能只添加新的重载,因为那样 Test("abc")
是不明确的,不再编译。
编辑:正如 canton7 所指出的:您可以添加一个 Test(string a)
方法来避免 that 歧义,但是:其他方法仍然存在(见下文),这进一步使API表面。
如果我们只关心二进制兼容性,我们可以这样做:
public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options = null) { ... }
但这会破坏 code 兼容性,因为现有代码 Test("abc", "def")
不再编译。我们不能只使 second 参数成为强制参数,因为如果我们这样做,现有代码 Test("abc", c: 42)
将不再编译。
如果您关心最佳代码兼容性,老实说,最好的选择是不使 options
可选。例如:
public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options) { ... }
这解决了几乎所有的代码兼容性问题。还有一种极端情况:Test("abc", null)
- 所以:你有多关心它?
如果你问的是如何重载第二个签名,应该这样做:
public void Test(string a, TestOptions options = null) =>
Test(a, options?.b, options?.c, options?.d);
但是,我怀疑您是否需要现代 C# 中的额外 class。如果这是一个简洁明了的问题(即,你只需要提供后面的参数,你也提供所有其他参数),你可以简单地使用命名参数调用:
Test("meep", d: "moop");
您可以将 TestOptions
更改为 struct
。但是您不能在重载中将它作为可为 null 的参数应用。
public struct TestOptions
{
public string b { get; set; }
public int? c { get; set; }
public string d { get; set; }
}
添加具有此类不可为 null 的结构参数的重载
public static void Test(string a, TestOptions options)
{ }
有了这个,下面的调用就不会有歧义了。
两者都将调用 old/first Test(string a, string b = null, int? c = null, string d = null)
方法,保留这个新重载之前的初始意图。
Test("ab");
Test("ab", null);
以前我有这样的方法:
public void Test(string a, string b = null, int? c = null, string d = null)
重新设计后,我们决定将所有可选参数分组到一个 class 中,如下所示:
public class TestOptions
{
public TestOptions()
{
}
public string b { get; set; }
public int? c { get; set; }
public string d { get; set; }
}
所以方法内部实现不会改变但是签名会变成:
public void Test(string a, TestOptions options = null)
我的问题是,第一个三个可选参数的方法已经在使用了,如果我现在想添加这个新方法,应该如何通过重载实现向后兼容?
这取决于你关心的是代码兼容性还是二进制兼容性;后者通常是强制性的(为了不破坏编译后的代码),但前者可以更灵活一些。
你不能只添加新的重载,因为那样 Test("abc")
是不明确的,不再编译。
编辑:正如 canton7 所指出的:您可以添加一个 Test(string a)
方法来避免 that 歧义,但是:其他方法仍然存在(见下文),这进一步使API表面。
如果我们只关心二进制兼容性,我们可以这样做:
public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options = null) { ... }
但这会破坏 code 兼容性,因为现有代码 Test("abc", "def")
不再编译。我们不能只使 second 参数成为强制参数,因为如果我们这样做,现有代码 Test("abc", c: 42)
将不再编译。
如果您关心最佳代码兼容性,老实说,最好的选择是不使 options
可选。例如:
public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options) { ... }
这解决了几乎所有的代码兼容性问题。还有一种极端情况:Test("abc", null)
- 所以:你有多关心它?
如果你问的是如何重载第二个签名,应该这样做:
public void Test(string a, TestOptions options = null) =>
Test(a, options?.b, options?.c, options?.d);
但是,我怀疑您是否需要现代 C# 中的额外 class。如果这是一个简洁明了的问题(即,你只需要提供后面的参数,你也提供所有其他参数),你可以简单地使用命名参数调用:
Test("meep", d: "moop");
您可以将 TestOptions
更改为 struct
。但是您不能在重载中将它作为可为 null 的参数应用。
public struct TestOptions
{
public string b { get; set; }
public int? c { get; set; }
public string d { get; set; }
}
添加具有此类不可为 null 的结构参数的重载
public static void Test(string a, TestOptions options)
{ }
有了这个,下面的调用就不会有歧义了。
两者都将调用 old/first Test(string a, string b = null, int? c = null, string d = null)
方法,保留这个新重载之前的初始意图。
Test("ab");
Test("ab", null);