当两个types参数相同时,在两个构造函数之间消除歧义

特定

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

当两个types参数相同时如何消除这两个构造函数之间的歧义?

例如,这一行:

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

失败:

这个调用在下列方法或属性之间是不明确的:'​​Program.Either.E(A)'和'Program.Either.Either(B)'

我知道如果我给了参数不同的名称(例如A aB b而不是只是x ),我可以使用命名参数来消除歧义(例如new Either<string, string>(a: "") )。 但是我有兴趣知道如何解决这个问题而不改变Either的定义。

编辑:

可以写一些聪明的构造函数,但是我很想知道是否可以直接调用它的构造函数而没有歧义。 (或者如果除此之外还有其他“技巧”)。

 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>(""); 

当两个types参数相同时如何消除这两个构造函数之间的歧义?

我将首先不回答你的问题,然后用一个实际的答案完成它,让你解决这个问题。

你不必因为你不应该首先进入这个位置 。 创build一个通用types可能会导致成员签名以这种方式统一是一个devise错误。 永远不要写这样的课。

如果你回头阅读原始的C#2.0规范,你会发现最初的devise是让编译器检测到可能出现这种问题的genericstypes,并且使类声明是非法的。 这使得它成为公布的规范,虽然这是一个错误; devise团队意识到这个规则太严格了,因为这样的场景:

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

如果说这个类是非法的,因为你可能会说C<Stream> ,然后无法消除构造函数的歧义,那将是很奇怪的。 相反,重载决议中增加了一条规则,即如果在(Stream)(T where Stream is substituted for T)之间有一个select,则前者获胜。

因此,这种统一是非法的规则被废弃了,现在被允许。 然而,以这种方式来统一types是一个非常非常糟糕的主意。 CLR在某些情况下处理得不好,编译器和开发人员都感到困惑。 例如,你会不会猜测这个程序的输出?

 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); } } 

如果您在编译时打开了警告,您将看到我添加的警告,解释了为什么这是一个非常糟糕的主意。

不,真的:当两个types参数相同时如何消除两个构造函数之间的歧义?

喜欢这个:

 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"); 

这就是class级本来应该如何devise的Either类的作者都应该写下来

 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"); 

我能想到的唯一方法是使用reflection来迭代每个构造函数,然后根据方法体确定应该使用哪一个。

当然,这是超越上层,你应该真的重构你的课堂,但这是一个工作的解决scheme。

它要求你确定要使用的方法体的byte[] ,并将“硬编码”标识到程序中(或者从文件等中读取)。 当然,你需要非常小心,方法体可能会随着时间的推移而变化,例如,如果在任何时候修改了这个类。

 // 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; } } 

免责声明 :虽然我已经简单地testing了这个代码,并且似乎可行,但我对它的可靠性持怀疑态度。 我不知道足够的编译器说,即使代码没有改变,方法体在重新编译时也不会改变。 如果这个类是在一个预编译的DLL中定义的,那么它可能会变得更加可靠,但我也不确定。


可能有其他信息可以通过reflection来获得,这可以使识别正确的构造函数更容易。 不过,这是第一个想到的,我目前还没有真正考虑过其他可能的select。

如果我们可以依赖构造函数的顺序就简单多了,但是从MSDN引用它并不可靠:

GetConstructors方法不返回特定顺序的构造函数,如声明顺序。 你的代码不能依赖于构造函数的返回顺序,因为顺序是不一样的。