Disambiguate between two constructors, when two type parameters are the same

How to disambiguate between the two constructors when the two type parameters are the same?

I’ll start by not answering your question, and then finish it up with an actual answer that lets you work around this problem.

You don’t have to because you should never get yourself into this position in the first place. It is a design error to create a generic type which can cause member signatures to be unified in this manner. Never write a class like that.

If you go back and read the original C# 2.0 specification you’ll see that the original design was to have the compiler detect generic types in which it was in any way possible for this sort of problem to arise, and to make the class declaration illegal. This made it into the published specification, though that was an error; the design team realized that this rule was too strict because of scenarios like:

class C<T> 
{
  public C(T t) { ... }
  public C(Stream s) { ... deserialize from the stream ... }
}

It would be bizarre to say that this class is illegal because you might say C<Stream> and then be unable to disambiguate the constructors. Instead, a rule was added to overload resolution which says that if there’s a choice between (Stream) and (T where Stream is substituted for T) then the former wins.

Thus the rule that this kind of unification is illegal was scrapped and it is now allowed. However it is a very, very bad idea to make types that unify in this manner. The CLR handles it poorly in some cases, and it is confusing to the compiler and the developers alike. For example, would you care to guess at the output of this program?

using System;
public interface I1<U> {
    void M(U i);
    void M(int i);
}

public interface I2<U> {
    void M(int i);
    void M(U i);
}

public class C3: I1<int>, I2<int> {
    void I1<int>.M(int i) {
        Console.WriteLine("c3 explicit I1 " + i);
    }
    void I2<int>.M(int i) {
        Console.WriteLine("c3 explicit I2 " + i);
    }
    public void M(int i) { 
        Console.WriteLine("c3 class " + i); 
    }
}

public class Test {
    public static void Main() {
        C3 c3 = new C3();
        I1<int> i1_c3 = c3;
        I2<int> i2_c3 = c3;
        i1_c3.M(101);
        i2_c3.M(102);
    }
}

If you compile this with warnings turned on you will see the warning I added explaining why this is a really, really bad idea.

No, really: How to disambiguate between the two constructors when the two type parameters are the same?

Like this:

static Either<A, B> First<A, B>(A a) => new Either<A, B>(a);
static Either<A, B> Second<A, B>(B b) => new Either<A, B>(b);
...
var ess1 = First<string, string>("hello");
var ess2 = Second<string, string>("goodbye");

which is how the class should have been designed in the first place. The author of the Either class should have written

class Either<A, B> 
{
  private Either(A a) { ... }
  private Either(B b) { ... }
  public static Either<A, B> First(A a) => new Either<A, B>(a);
  public static Either<A, B> Second(B b) => new Either<A, B>(b);
  ...
}
...
var ess = Either<string, string>.First("hello");

Leave a Comment