【问题标题】:Equivalent implicit operators: why are they legal?等效的隐式运算符:为什么它们是合法的?
【发布时间】:2010-08-24 22:48:56
【问题描述】:

更新!

请参阅下面我对 C# 规范的一部分的剖析;我想我一定遗漏了什么,因为对来说,我在这个问题中描述的行为似乎实际上违反了规范。

更新 2!

好的,经过进一步思考,并基于一些 cmets,我想我现在明白发生了什么。规范中的“源类型”一词指的是要转换的类型 from ——即,在我下面的示例中,Type2 ——这仅仅意味着编译器能够将候选者缩小到定义的两个运算符(因为Type2 是两者的源类型)。但是,它不能进一步缩小选择范围。所以规范中的关键词(因为它适用于这个问题)是“源类型”,我之前误解(我认为)意思是“声明类型”。


原始问题

假设我定义了这些类型:

class Type0
{
    public string Value { get; private set; }

    public Type0(string value)
    {
        Value = value;
    }
}

class Type1 : Type0
{
    public Type1(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type1's operator.");
    }
}

class Type2 : Type0
{
    public Type2(string value) : base(value) { }

    public static implicit operator Type1(Type2 other)
    {
        return new Type1("Converted using Type2's operator.");
    }
}

然后说我这样做:

Type2 t2 = new Type2("B");
Type1 t1 = t2;

显然这是模棱两可的,因为不清楚应该使用哪个implicit 运算符。我的问题是——因为我看不到解决这种歧义的 any 方法(我不能执行一些明确的强制转换来澄清我想要的版本),但是上面的类定义确实可以编译-- 为什么编译器会允许那些匹配的implicit 运算符?


解剖

好的,我将逐步阅读 H​​ans Passant 引用的 C# 规范的摘录,以试图理解这一点。

找到类型集 D,从中 用户定义的转换运算符将 被考虑。这组包括 S (如果 S 是一个类或结构),基 S 的类(如果 S 是一个类),以及 T (如果 T 是一个类或结构)。

我们将 Type2 (S) 转换为 Type1 (T)。所以这里的 D 似乎会包含示例中的所有三种类型:Type0(因为它是 S 的基类)、Type1 T) 和Type2 (S)。

找到适用的集合 用户定义的转换运算符,U。 该集合包括用户定义的 声明的隐式转换运算符 通过 D 中的类或结构 从包含 S 的类型转换为 T 包含的类型。如果 U 是 为空,转换未定义并且 发生编译时错误。

好的,我们有两个满足这些条件的运算符。 Type1 中声明的版本符合要求,因为 Type1 位于 D 中,它从 Type2(显然包含 S)转换为 Type1(其中显然被 T 包含)。 Type2 中的版本 出于完全相同的原因满足要求。所以 U 包括这两个运算符。

最后,关于在 U 中找到最具体的“源类型”SX 运算符:

如果 U 中的任何运算符从 S 转换,则 SX 是 S。

现在,U 中的 both 运算符从 S 转换——所以这告诉我 SX 是 S。

不是说要使用Type2的版本吗?

但是等等!我很困惑!

难道我不能定义Type1 的运算符版本,在这种情况下,唯一剩下的候选者将是Type1 的版本,但根据规范SX 会是Type2?这似乎是一种可能的情况,其中规范要求一些不可能的事情(即,在 Type2 中声明的转换实际上不存在时应该使用它)。

【问题讨论】:

  • 一个声明可能优先于另一个。编译器选择哪一个?
  • @dtb:它允许类定义,但不允许底部的示例代码。它指出存在模棱两可的用户定义转换。换句话说,它允许代码编译,但绝不允许它被使用
  • 我会指出,如果有多个用户定义的显式转换,它会做同样的事情。
  • @mquander:嗯,至少它是一致的,那么 ;)
  • 它可能不会检查,因为(a)如果两个类位于不同的命名空间中会很好,并且(b)它不知道冲突的声明是否在同一个命名空间中但在不同的命名空间中部件。所以当你声明它时,它会检查你何时使用它。

标签: c# .net implicit ambiguity implicit-conversion


【解决方案1】:

最终,不能完全成功地禁止它。你和我可以发布两个程序集。我们可以开始使用彼此的程序集,同时更新我们自己的程序集。然后我们每个人都可以在每个程序集中定义的类型之间提供隐式转换。只有在我们发布下一个版本时,才能捕捉到这一点,而不是在编译时。

不试图禁止不能禁止的东西是有好处的,因为它有助于清晰和一致(这对立法者来说是一个教训)。

【讨论】:

    【解决方案2】:

    我们真的不希望它只是一个编译时错误,只是为了定义可能导致歧义的转换。假设我们将 Type0 更改为存储双精度数,并且出于某种原因我们希望提供对有符号整数和无符号整数的单独转换。

    class Type0
    {
        public double Value { get; private set; }
    
        public Type0(double value)
        {
            Value = value;
        }
    
        public static implicit operator Int32(Type0 other)
        {
            return (Int32)other.Value;
        }
    
        public static implicit operator UInt32(Type0 other)
        {
            return (UInt32)Math.Abs(other.Value);
        }
    
    }
    

    这编译得很好,我可以同时使用这两种转换

    Type0 t = new Type0(0.9);
    int i = t;
    UInt32 u = t;
    

    但是,尝试 float f = t 时会出现编译错误,因为任何一种隐式转换都可用于获取整数类型,然后再将其转换为浮点数。

    我们只希望编译器在实际使用这些更复杂的歧义时抱怨它们,因为我们希望上面的 Type0 能够编译。为了保持一致性,更简单的歧义也应该在您使用它而不是在您定义它时导致错误。

    编辑

    由于 Hans 删除了引用规范的答案,这里快速浏览了 C# 规范中确定转换是否不明确的部分,将 U 定义为可能完成这项工作的所有转换的集合:

    • 在 U 中查找最具体的源类型 SX:
      • 如果 U 中的任何运算符从 S 转换,则 SX 是 S。
      • 否则,SX 是 U 中运算符的目标类型组合集中包含最多的类型。如果找不到最多包含的类型,则转换不明确并发生编译时错误。

    换句话说,我们更喜欢直接从 S 转换的转换,否则我们更喜欢“最容易”将 S 转换为的类型。在这两个示例中,我们都有两个可用的 S 转换。如果没有来自Type2 的转化,我们更喜欢来自Type0 的转化而不是来自object 的转化。如果没有一种类型显然是转换的更好选择,那么我们在这里失败了。

    • 在 U 中查找最具体的目标类型 TX:
      • 如果 U 中的任何运算符转换为 T,则 TX 为 T。
      • 否则,TX 是 U 中运算符的目标类型组合集中最包含的类型。如果找不到最包含的类型,则转换不明确并发生编译时错误。

    同样,我们更愿意直接转换为 T,但我们会选择“最容易”转换为 T 的类型。在 Dan 的示例中,我们有两个可用的 T 转换。在我的示例中,可能的目标是Int32UInt32,两者都没有比另一个更好的匹配,所以这就是转换失败的地方。编译器无法知道float f = t 是指float f = (float)(Int32)t 还是float f = (float)(UInt32)t

    • 如果 U 恰好包含一个用户定义的从 SX 转换为 TX 的转换运算符,那么这是最具体的转换运算符。如果不存在这样的运算符,或者存在多个这样的运算符,则转换不明确并发生编译时错误。

    在 Dan 的示例中,我们在这里失败了,因为我们还有两个从 SX 到 TX 的转换。如果我们在决定 SX 和 TX 时选择不同的转换,我们可能没有从 SX 到 TX 的转换。例如,如果我们有一个从Type1 派生的Type1a,那么我们可能会有从Type2Type1a 以及从Type0Type1 的转换,这些仍然会给我们SX=Type2 和TX=Type1 ,但我们实际上并没有从 Type2 到 Type1 的任何转换。这没关系,因为这确实是模棱两可的。编译器不知道是先将 Type2 转换为 Type1a,然后再转换为 Type1,还是先转换为 Type0,然后才能使用转换为 Type1。

    【讨论】:

    • 好的,我喜欢这个解释。我也喜欢您对汉斯(现已删除)答案的评论——您愿意将该评论的内容合并到这个答案中吗?不管怎样,我觉得这是值得接受的。
    猜你喜欢
    • 2010-09-27
    • 2019-02-06
    • 2012-05-15
    • 1970-01-01
    • 2020-06-26
    • 1970-01-01
    • 2012-01-24
    相关资源
    最近更新 更多