Disambigutate between two constructors when two type parameters are the same

Considering

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

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

For example, this line:

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

Failure:

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

I know that if I gave the parameters different names (for example, A a and B b instead of just x ), I could use named parameters to disambiguate (for example, new Either<string, string>(a: "") ) . But I am interested to know how to solve this problem without changing the definition of Either .

Edit:

You can write a couple of smart constructors, but I'm curious to know if Either constructors can be called directly without ambiguity. (Or if there are other "tricks" besides this).

 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>(""); 
+46
c #
Sep 12 '17 at 8:22 on
source share
2 answers

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

To begin with, I will not answer your question, and then I will end it with an actual answer that will allow you to get around this problem.

You do not need, because you should never get into this position in the first place . A design error to create a typical type that can lead to signature signatures being unified in this way. Never write such a class.

If you come back and read the original C # 2.0 specification, you will see that the original design had to have the compiler detect common types in which it was possible in any way to cause such a problem, and to make the class declaration forbidden. This made it into the published specification, although it was a mistake; The development team realized that this rule was too strict due to scenarios such as:

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

It would be strange to say that this class is illegal because you can tell C<Stream> and then you cannot unambiguously eliminate the constructors. Instead, a rule was added to allow congestion, which states that if there is a choice between (Stream) and (T where Stream is substituted for T) , then the first wins.

Thus, the rule that this type of association is illegal has been repealed, and now it is allowed. However, a very, very bad idea is to create types that combine in this way. In some cases, the CLR does a poor job of this, and this confuses the compiler and developers. For example, would you like to guess 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 a warning that I added explaining why this is a really and really bad idea.

No, really: how to eliminate the ambiguity between two constructors when 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"); 

how the class should have been designed in the first place . Class author Either should write

 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"); 
+49
Sep 12 '17 at 9:46 on
source share

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

Of course, this is the way from above, and you really should just reorganize your class, but this is a working solution.

This requires identifying byte[] for the body of the method you want to use and the "hard code" contained in the program (or reading from a file, etc.). Of course, you need to be very careful so that the body of the method can change over time, for example, if the class is changed 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 briefly tested this code and it seems to work, I am really skeptical about how reliable it is. I don’t know enough about the compiler to make it convenient to say that the body of the method will not change upon recompilation, even if the code has not changed. It might become more reliable if this class were defined in a precompiled DLL, but I don't know for sure.




There may be other information that you could get through reflection, which could facilitate the identification of the correct constructor. However, this was the first thing that occurred to me, and I did not really look at other possible options.

It would be much simpler if we could rely on the order of the constructors, but as stated on MSDN , this is unreliable:

The GetConstructors method does not return constructors in a specific order, such as the declaration order. Your code should not depend on the order in which the constructors are returned, because this order is changing.

+3
Sep 12 '17 at 9:14 on
source share



All Articles