当两个类型参数相同时,消除两个构造函数之间的歧义

dca*_*tro 46 c#

特定

class Either<A, B> {

    public Either(A x) {}
    public Either(B x) {}
}
Run Code Online (Sandbox Code Playgroud)

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

例如,这一行:

var e = new Either<string, string>("");
Run Code Online (Sandbox Code Playgroud)

失败:

以下方法或属性之间的调用不明确:'Program.Either.Either(A)'和'Program.Either.Either(B)'

我知道,如果我给出的参数不同的名称(例如,A aB b,而不是仅仅x),我可以使用命名参数来消除歧义(例如new Either<string, string>(a: "")).但我很想知道如何在不改变定义的情况下解决这个问题Either.

编辑:

可以编写几个智能构造函数,但我很想知道是否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>("");
Run Code Online (Sandbox Code Playgroud)

Eri*_*ert 49

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

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

你不必这样做,因为你不应该首先让自己进入这个位置.创建泛型类型是一种设计错误,可以使成员签名以这种方式统一.永远不要写那样的课.

如果你回头看原来的C#2.0规范,你会发现原来的设计是有编译器检测的通用类型,它是在任何可能的方式为这种问题出现,并且使类声明非法.这使它成为已发布的规范,尽管这是一个错误; 设计团队意识到这条规则过于严格,因为以下情况:

class C<T> 
{
  public C(T t) { ... }
  public C(Stream s) { ... deserialize from the stream ... }
}
Run Code Online (Sandbox Code Playgroud)

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

因此,这种统一是非法的规则被废除,现在允许.然而,制作以这种方式统一的类型是一个非常非常糟糕的主意.在某些情况下,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你在打开警告的情况下编译它,你会看到我添加的警告,解释为什么这是一个非常非常糟糕的主意.

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

像这样:

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");
Run Code Online (Sandbox Code Playgroud)

这就是本课程应该首先设计的方式.该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");
Run Code Online (Sandbox Code Playgroud)

  • @Damien_The_Unbeliever:在我开始编写答案后添加了该编辑. (7认同)
  • @dcastro奇怪的是,这取决于源代码顺序.它不在规范中.编译器尽其所能,但关于哪些方法绑定到哪个接口槽的决定由CLR实现决定.实际上,接口实现表中的粒度不足以让编译器说出它更喜欢的内容!这是仿制药实施中的一个小设计缺陷; 对于这种模糊的场景,修复它的成本太高了. (6认同)
  • 在OPs问题中,"First"和"Second"不是"Left"和"Right"相同吗? (3认同)
  • @dcastro:对你提出这个解决方案很好; 它绝不是显而易见的! (3认同)
  • @EricLippert关于我的编辑,我会承担责任.在发布问题后我迅速提出了这个解决方案 - 我可能应该补充一点,作为我自己问题的答案(对于未来的读者),而不是编辑问题.即便如此,我想知道是否还有其他的,或许在某些方面更好的解决方案. (2认同)