代码之家  ›  专栏  ›  技术社区  ›  Andreas Grech

“ref”和“out”为什么不支持多态性?

  •  119
  • Andreas Grech  · 技术社区  · 16 年前

    采取以下措施:

    class A {}
    
    class B : A {}
    
    class C
    {
        C()
        {
            var b = new B();
            Foo(b);
            Foo2(ref b); // <= compile-time error: 
                         // "The 'ref' argument doesn't match the parameter type"
        }
    
        void Foo(A a) {}
    
        void Foo2(ref A a) {}  
    }
    

    为什么会出现上述编译时错误?这两种情况都会发生 ref out 争论。

    10 回复  |  直到 7 年前
        1
  •  161
  •   Jeroen Vannevel luciomrx    11 年前

    企业的价值

    更新:我将此答案用作此日志的基础:

    Why do ref and out parameters not allow type variation?

    有关此问题的更多评论,请参阅博客页面。谢谢你的提问。

    企业的价值

    假设你有课 Animal , Mammal , Reptile , Giraffe , Turtle Tiger 具有明显的子类关系。

    现在假设你有一个方法 void M(ref Mammal m) . M 既能读又能写 m .


    你能传递一个类型的变量吗 动物 ?

    不。该变量可以包含 乌龟 但是 哺乳动物 .

    结论1 : ref 参数不能设为“大”。(动物比哺乳动物多,所以变量变得“大”,因为它可以包含更多的东西。)


    你能传递一个类型的变量吗 长颈鹿 ?

    不。 可以写信给 可能想写一个 老虎 进入之内 . 现在你把 老虎 变为实际类型的变量 长颈鹿 .

    结论2 : 裁判 不能将参数设为“较小”。


    现在考虑 N(out Mammal n) .

    你能传递一个类型的变量吗 长颈鹿 N ?

    不。 n 可以写信给 n n 可能想写一个 老虎 .

    结论3 : out 不能将参数设为“较小”。


    你能传递一个类型的变量吗 动物 n ?

    隐马尔可夫模型。

    嗯,为什么不呢? n 无法读取 n ,它只能写给它,对吗?你写的 老虎 到类型的变量 动物 你已经准备好了,对吗?

    错了。规则不是” n 只能写入 n “。

    简单地说,规则是:

    1) n 不得不写信给 n 之前 n 正常返回。(如果 n 掷骰子,所有赌注都取消。)

    2) n 必须写些东西给 n 在它读到之前 n .

    这就允许了这一系列事件:

    • 声明字段 x 类型的 动物 .
    • 通过 X 作为一个 外面的 参数到 n .
    • n 写一个 老虎 进入之内 n ,它是的别名 X .
    • 在另一个线程上,有人写了 乌龟 进入之内 X .
    • n 尝试读取的内容 n 并发现 乌龟 它认为的是一个类型的变量 哺乳动物 .

    很明显,我们想把它定为非法的。

    结论4 : 外面的 参数不能设为“大”。


    最后结论 : 既不 裁判 也不 外面的 参数的类型可能有所不同。否则将破坏可验证的类型安全。

    如果你对这些基本类型理论的问题感兴趣,可以考虑阅读 my series on how covariance and contravariance work in C# 4.0 .

        2
  •  28
  •   maciejkow    16 年前

    因为在这两种情况下,您都必须能够为ref/out参数赋值。

    如果尝试将b作为引用传递到foo2方法中,并且在foo2中尝试分配a=new a(),那么这将是无效的。
    你不能写作的同样原因:

    B b = new A();
    
        3
  •  9
  •   Alex Martelli    16 年前

    你正在努力解决经典的OOP问题 协方差 (和反差),见 wikipedia :尽管这一事实可能违背了直观的期望,但在数学上不可能允许用派生类代替基类来替换可变(可分配)参数(以及其项可分配的容器,原因完全相同),同时仍然尊重 Liskov's principle .为什么会这样呢?在现有答案中略图,并在这些维基文章和链接中更深入地探讨了这一点。

    OOP语言在保持传统的静态类型安全的情况下看起来是“欺骗”(插入隐藏的动态类型检查,或者需要对所有源进行编译时检查);最基本的选择是:要么放弃这种协方差,接受从业者的困惑(就像C在这里做的那样),要么转向动态类型Approach(作为第一种OOP语言,smalltalk,did)或移动到不可变(单次赋值)数据,就像函数语言一样(在不可变情况下,您可以支持协方差,也可以避免其他相关的难题,例如,在可变的数据世界中不能有方形子类矩形)。

        4
  •  4
  •   Henk Holterman    10 年前

    考虑:

    class C : A {}
    class B : A {}
    
    void Foo2(ref A a) { a = new C(); } 
    
    B b = null;
    Foo2(ref b);
    

    会违反类型安全

        5
  •  2
  •   CannibalSmith    16 年前

    因为给予 Foo2 ref B 会导致对象格式错误,因为 氧分压 只知道怎么填 A 部分 B .

        6
  •  1
  •   BrendanLoBuglio    7 年前

    虽然其他回答已经简洁地解释了这种行为背后的原因,但我认为值得一提的是,如果您确实需要做这种性质的事情,您可以通过将foo2变成一种通用方法来完成类似的功能,例如:

    class A {}
    
    class B : A {}
    
    class C
    {
        C()
        {
            var b = new B();
            Foo(b);
            Foo2(ref b); // <= no compile error!
        }
    
        void Foo(A a) {}
    
        void Foo2<AType> (ref AType a) where AType: A {}  
    }
    
        7
  •  0
  •   dlamblin    16 年前

    编译器不是告诉您它希望您显式地强制转换对象以确保您知道您的意图吗?

    Foo2(ref (A)b)
    
        8
  •  0
  •   Oofpez    13 年前

    从安全的角度讲是有意义的,但是我更愿意编译器给出警告而不是错误,因为通过引用传递的多数学对象是合法使用的。例如

    class Derp : interfaceX
    {
       int somevalue=0; //specified that this class contains somevalue by interfaceX
       public Derp(int val)
        {
        somevalue = val;
        }
    
    }
    
    
    void Foo(ref object obj){
        int result = (interfaceX)obj.somevalue;
        //do stuff to result variable... in my case data access
        obj = Activator.CreateInstance(obj.GetType(), result);
    }
    
    main()
    {
       Derp x = new Derp();
       Foo(ref Derp);
    }
    

    这不会编译,但它能工作吗?

        9
  •  0
  •   Ian Boyd    8 年前

    如果您对类型使用实际示例,您将看到:

    SqlConnection connection = new SqlConnection();
    Foo(ref connection);
    

    现在你有了你的功能 祖先 ( Object ):

    void Foo2(ref Object connection) { }
    

    那可能有什么问题?

    void Foo2(ref Object connection)
    {
       connection = new Bitmap();
    }
    

    你刚刚设法分配了一个 Bitmap 对你 SqlConnection .

    那不好。


    与其他人一起重试:

    SqlConnection conn = new SqlConnection();
    Foo2(ref conn);
    
    void Foo2(ref DbConnection connection)
    {
        conn = new OracleConnection();
    }
    

    你塞满了 OracleConnection 在你的上面 连接对象 .

        10
  •  0
  •   Shereef Marzouk    7 年前

    在我的例子中,我的函数接受了一个对象,我不能发送任何东西,所以我只是做了

    object bla = myVar;
    Foo(ref bla);
    

    那作品

    我的foo在vb.net中,它检查内部的类型并执行大量的逻辑操作

    如果我的回答是重复的,我道歉,但其他人太长了

    推荐文章