Home > Net >  Backward compatible when using three optional parameters vs one optional parameters
Backward compatible when using three optional parameters vs one optional parameters

Time:04-28

Previously I had a method like this:

public void Test(string a, string b = null, int? c = null, string d = null)

After the redesign, we decided to group all the optional parameters to a class which like this:

public class TestOptions
{
    public TestOptions()
    {
    }

    public string b { get; set; }

    public int? c { get; set; }

    public string d { get; set; }
}

So the method internal implementation will not change but the signature will become:

public void Test(string a, TestOptions options = null)

My question is, since the first method with three optional parameters is already in use, if I want to add this new method now, how should I achieve backward compatibility by overloading?

CodePudding user response:

It depends on whether you care about code compatibility vs binary compatibility; the latter is usually mandatory (to not break compiled code), but the former can be a little more flexible.

You can't just add the new overload, because then Test("abc") is ambiguous and no longer compiles.

Edit: as canton7 notes: you could add a Test(string a) method to avoid that ambiguity, but: others remain (see below), and it further complicates the API surface.

If we only care about binary compatability, we can do:

public void Test(string a, string b, int? c, string d)
    => Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options = null) { ... }

But this will break code compatibility, because the existing code Test("abc", "def") no longer compiles. We can't just make the second parameter mandatory, because if we do that, the existing code Test("abc", c: 42) no longer compiles.


If you care about the best code compatibility, honestly the best option is to not make options optional. For example:

public void Test(string a, string b, int? c, string d)
    => Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options) { ... }

This solves almost all problems of code compatibility. There's still one edge case: Test("abc", null) - so: how much do you care about that?

CodePudding user response:

If you're asking how to method overload the second signature, something like this should do it:

public void Test(string a, TestOptions options = null) =>
    Test(a, options?.b, options?.c, options?.d);

However, I question your need for the extra class in modern C#. If it's a matter of conciseness and clarity (ie, you only ever need to provide the latter parameters and you provide all the other ones too), you could simply use named argument calling:

Test("meep", d: "moop");

CodePudding user response:

For backwards compatibility, you might change that TestOptions into a struct. But you can't apply it as a nullable argument in your overload.

public struct TestOptions
{
    public string b { get; set; }

    public int? c { get; set; }

    public string d { get; set; }
}

Add an overload having such a non nullable struct argument

public static void Test(string a, TestOptions options)
{ }

With that in place there won't be ambiguity for the calls below.
Both will call the old/first Test(string a, string b = null, int? c = null, string d = null) method, preserving the initial intend as before this new overload was in place.

Test("ab");
Test("ab", null);
  • Related