Whisperity
Whisperity

Reputation: 3042

Require parameter naming to prevent ambiguity on same-ish looking parameter list

Consider the following definition:

class Foo
{
    string Asd;
    List<int> Qwert;

    public Foo(string Bar, List<int> Baz = null)
    {
        // Set up instance from parameters
        this.Asd = Bar;
        this.Qwert = Baz ?? new List<int>;
    }

    public Foo(string Qwop)
    {
        // Code which sets up the instance based on what Qwop is
        // E.g.: take Asd and Qwert values from a previous storage
    }
}

Now if I invoke the constructor as Foo foo = new Foo("value"); the second constructor is executed.

How can I make the compiler force the invocation with named parameters? Because both constructors' first argument is a string, there is a clear ambiguity. (I cannot escape or workaround the fact that either way, we need to take a string.)

Foo foo = new Foo("value"); // NOT fine
Foo foo = new Foo(Bar: "value"); // Fine, first constructor
Foo foo = new Foo("value", new List<int> { 0, 1, 2 }; // Fine, as it is clearly 1st
Foo foo = new Foo(Qwop: "anothervalue"); // Fine, second constructor

I thought about declaring an extra constructor:

[Obsolete("Parameterization would be ambiguos. Please specify what string is by using named parameters", true)]
public Foo(string param)
{
    throw new ArgumentException();
}

I thought this would mean that without named parameters, the code would call this one and compiling would fail. But the build failed earlier, because

Type 'Program.Foo' already defines a member called 'Foo' with the same parameter types

So apparently, this is a no-go. But my other try:

class Foo
{
    public Foo(IFooInvoker invoker)
    {
        if (invoker is FooInvokerBarBaz) { ... }
        else if (invoker is FooInvokerQwop) { ... }
    }
}

interface IFooInvoker { ... }

class FooInvokerBarBaz : IFooInvoker { ... }
class FooInvokerQwop : IFooInvoker { ... }

// And calling the whole from Main by
Foo foo = new Foo(new FooInvokerBarBaz("bar", new List<int> { 0, 1, 2 });
// or
Foo foo = new Foo(new FooInvokerQwop("qwop");

Looks way too complicated, is ugly and I think violates more than one design principles.

Upvotes: 1

Views: 106

Answers (2)

Tim S.
Tim S.

Reputation: 56556

No, you can't. C# just doesn't support it. You could make your signatures less similar, e.g.

public Foo(string bar, List<int> baz) // baz is now required, but can be null
public Foo(string qwop)

Or you could make the constructors private and expose static methods:

private Foo(string bar, List<int> baz)
private Foo(string qwop)
public static Foo FromBarBaz(string bar, List<int> baz = null)
public static Foo FromQwop(string qwop)

(as a minor note, parameters should be camelCased, not PascalCased)

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1502716

How can I make the compiler force the invocation with named parameters?

You can't, basically. It's just not part of the language.

What you can do is make the constructors private, and instead expose static factory methods, which explain what they're constructing an instance from.

public static Foo FromBarAndBaz(string bar, List<int> baz = null)
{
    return new Foo(bar, baz);
}

public static Foo FromQwop(string qwop)
{
    return new Foo(qwop);
}

(You'd probably want to make baz mandatory in the constructor too, for simplicity.)

Note that I've changed the parameter names to comply with normal .NET naming conventions.

Upvotes: 5

Related Questions