Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

Given

class Either<A, B> {      public Either(A x) {}     public Either(B x) {} } 

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

For example, this line:

var e = new Either<string, string>(""); 

Fails with:

The call is ambiguous between the following methods or properties: 'Program.Either.Either(A)' and 'Program.Either.Either(B)'

I know if I had given the parameters different names (e.g. A a and B b instead of just x), I could use named parameters to disambiguate (e.g. new Either<string, string>(a: "")). But I'm interested in knowing how to solve this without changing the definition of Either.

Edit:

You can write a couple of smart constructors, but I'm interested in knowing if the Either's constructors can be called directly without ambiguity. (Or if there are other "tricks" besides this one).

static Either<A, B> Left<A, B>(A x) {     return new Either<A, B>(x); }  static Either<A, B> Right<A, B>(B x) {     return new Either<A, B>(x); }  var e1 = Left<string, string>(""); var e2 = Right<string, string>(""); 
like image 384
dcastro Avatar asked Sep 12 '17 08:09

dcastro


People also ask

Can two constructors have the same number of parameters?

There can be multiple constructors in a class. However, the parameter list of the constructors should not be same. This is known as constructor overloading.

Can you have two constructors with the same name?

You can't have two constructors (or any functions) with the same signatures. The best solution is probably to create classes for your coordinate types and overload on those.

Why can more than one constructor have the same name and different arguments?

This concept is known as Constructor Overloading and is quite similar to function overloading. Overloaded constructors essentially have the same name (exact name of the class) and differ by number and type of arguments. A constructor is called depending upon the number and type of arguments passed.

What name is given to constructors with different sets of parameters?

Constructors Overloading in Java Similar to Java method overloading, we can also create two or more constructors with different parameters. This is called constructors overloading.


2 Answers

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"); 
like image 82
Eric Lippert Avatar answered Oct 06 '22 01:10

Eric Lippert


The only way I could think of would be to use reflection to iterate each constructor and then determine which one should be used based on the method body.

Of course this is way over the top and you should really just refactor your class, but it is a working solution.

It requires that you identify the byte[] for the method body that you want to use and 'hard code' that into the program (or read from file etc.). Of course you need to be very cautious that the method body may change over time, for example if the class is modified at any point.

// You need to set (or get from somewhere) this byte[] to match the constructor method body you want to use. byte[] expectedMethodBody = new byte[] { 0 };  Either<string, string> result = null; // Will hold the result if we get a match, otherwise null. Type t = typeof(Either<string, string>); // Get the type information.  // Loop each constructor and compare the method body. // If we find a match, then we invoke the constructor and break the loop. foreach (var c in t.GetConstructors()) {     var body = c.GetMethodBody();     if (body.GetILAsByteArray().SequenceEqual(expectedMethodBody))     {         result = (Either<string, string>)c.Invoke(new object[] { "123" });         break;     } } 

Disclaimer: Although I have tested this code briefly and it seems to work, I am really sceptical about how reliable it is. I do not know enough about the compiler to be comfortable in saying the method body wont change on a re-compile even if the code isn't changed. It may be that it would become more reliable if this class was defined in a pre-compiled DLL, again I don't know for sure though.


There may be other information you could get via reflection that might make it easier to identify the correct constructor. However, this was the first that came to mind and I haven't really looked into any other possible options at this time.

It would be much simpler if we could rely on the order of the constructors, but as quoted from MSDN, it is not reliable:

The GetConstructors method does not return constructors in a particular order, such as declaration order. Your code must not depend on the order in which constructors are returned, because that order varies.

like image 23
musefan Avatar answered Oct 06 '22 01:10

musefan