【问题标题】:Can you satisfy a generic constraint with an implicit conversion?您可以通过隐式转换满足通用约束吗?
【发布时间】:2011-11-18 02:25:43
【问题描述】:

鉴于这些类型:

class A { }
class B
{
    public static implicit operator A(B me)
    {
        return new A();
    }
}

class Test<T> where T : A { }

我试过了

var b = new Test<B>();

并预计它会失败,它确实做到了。但是错误信息是

类型“B”不能用作泛型类型或方法“Test”中的类型参数“T”。没有从“B”到“A”的隐式引用转换。

但是从B到A的隐式引用转换。这只是一个奇怪的消息吗?不是隐式引用转换正如亚当罗宾逊的回答所示。消息是正确的。

注意MSDN says:

其中 T :(基类名称)- 类型参数必须是指定的基类或派生自指定的基类。

这解释了为什么它是不允许的,因为B 不是从A 派生的

【问题讨论】:

    标签: c# generics implicit-conversion


    【解决方案1】:

    不,您尝试做的事情是不可能的。隐式 reference 转换与隐式 type 转换不同。您的代码定义了一个隐式类型转换,您可以在其中执行以下操作:

    B foo = new B();
    A bar = foo;
    

    但是请注意,foobar 现在包含不同的引用。隐式类型转换创建了一个Anew 实例,它应该(按照惯例)在逻辑上等同于foo。但关键是它是一个不同的参考。

    reference 转换将是引用本身不会改变的地方,这意味着所讨论的类型必须继承自(对于类)或实现(对于接口)所讨论的类型。如果我这样做:

    class A { }
    class B : A { }
    

    那么我上面的代码现在将在foobar 中保存相同的引用。这就是隐式引用转换的含义。相反,显式引用转换会向下转换,如下所示:

    A foo = new B();
    B bar = (B)foo;
    

    同样,引用是相同的,但演员表是明确的。

    因此,简而言之,MSDN 文档更清晰但不够精确。

    【讨论】:

    • +1 我不明白类型转换和引用转换之间的区别。
    • 我希望我们可以有通用约束来允许我们指定所需的转换 - 这在使用组合继承时会有所帮助,尽管我们仍然需要一些方法来允许隐式转换(和来自)接口类型转换,而不是引用转换。哦,好吧。
    【解决方案2】:

    这是不可能的。

    隐式转换不同于类型等效。仅仅因为一种类型可以转换为另一种类型并不意味着它是第二种类型的特定形式。因此,它不适用于通用约束。

    这很有道理——想想编译器会在下面做什么:

    class A 
    {
        public void Foo();
    }
    class B
    {
        public static implicit operator A(B me)
        {
            return new A();
        }
    }
    

    现在,假设你有:

    public void Bar<T>(T obj) where T : A
    {       
        obj.Foo();
        obj.Foo();
        obj.Foo();
    }
    

    为了使这项工作与转换一起工作(即:允许调用Bar(new B())) - 您必须在该方法内构造一个新对象实例,因为 Foo 未在 B 上定义。这将是非常出乎意料的,并且可能导致一些非常难以发现的错误。在上面,转换操作是否应该发生在每个方法调用上?它应该发生一次,编译器会做一些诡计来让它工作吗?虽然可以想出办法来处理这个问题,但没有一种方法是明确的......

    【讨论】:

      【解决方案3】:

      其他人主要介绍了它,但我想我会粘贴一些规范的东西

      被认为有效的完整列表在 c# 语言规范的第 6.1.6 章中。关键部分是最后一段说:

      引用转换,无论是隐式的还是显式的,都不会改变 正在转换的对象的引用标识。换句话说, 虽然引用转换可能会改变引用的类型,但它 从不改变被引用对象的类型或值。

      完整的转化列表如下:

      隐式引用转换为:

      • 从任何引用类型到对象和动态。
      • 从任何类类型 S 到任何类类型 T,前提是 S 派生自 T。
      • 从任何类类型 S 到任何接口类型 T,前提是 S 实现了 T。
      • 从任何接口类型 S 到任何接口类型 T,前提是 S 派生自 T。
      • 从元素类型为 SE 的数组类型 S 到元素类型为 TE 的数组类型 T,前提是满足以下所有条件:
        • S 和 T 仅在元素类型上有所不同。换言之,S 和 T 的维数相同。
        • SE 和 TE 都是引用类型。
        • 存在从 SE 到 TE 的隐式引用转换。
      • 从任何数组类型到 System.Array 及其实现的接口。
      • 从一维数组类型 S[] 到 System.Collections.Generic.IList 及其基接口,提供 存在从 S 到的隐式标识或引用转换 T.
      • 从任何委托类型到 System.Delegate 及其实现的接口。
      • 从 null 文字到任何引用类型。
      • 从任何引用类型到引用类型 T,如果它具有到引用类型 T0 的隐式标识或引用转换,并且 T0 有一个到 T 的恒等式转换。
      • 从任何引用类型到接口或委托类型 T,如果它具有到接口的隐式标识或引用转换或 委托类型 T0 和 T0 可方差转换(第 13.1.3.2 节)到 T。
      • 涉及已知为引用类型的类型参数的隐式转换。有关隐式的更多详细信息,请参见 §6.1.10 涉及类型参数的转换。

      【讨论】:

        【解决方案4】:

        在您的示例代码中,B 不是从A 派生的。试试

        class B : A
        { 
            public static implicit operator A(B me) 
            { 
                return new A(); 
            } 
        } 
        

        不,这不是一条奇怪的消息。 “隐式引用转换”(规范第 6.1.6 节)与您所拥有的“用户定义的隐式转换”(第 6.1.10 节)不同。

        引用转换意味着您可以将对给定对象的引用从一种类型转换为另一种类型(“引用转换......永远不会改变被转换对象的引用标识”)。

        用户定义的隐式转换可以(如您所做的那样)返回一个新的、不同的对象。

        【讨论】:

        • 呃...这仍然行不通(OP 在他的示例中明确避免继承),如果该代码是合法的,那么应该将其取出并拍摄。
        • @AdamRobinson 当你写之前的评论时,我还没有发布编辑。
        • @phoog:您可能应该在评论之前发布编辑,然后将人们推荐给它,因为我当然和亚当一样。
        猜你喜欢
        • 1970-01-01
        • 2014-12-02
        • 2012-07-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-16
        • 2022-01-19
        • 1970-01-01
        相关资源
        最近更新 更多