簡體   English   中英

為什么“ ref”和“ out”不支持多態?

[英]Why doesn't 'ref' and 'out' support polymorphism?

采取以下措施:

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

為什么會出現上述編譯時錯誤? refout參數都會發生這種情況。

=============

更新:我使用此答案作為此博客條目的基礎:

為什么ref和out參數不允許類型變化?

有關此問題的更多評論,請參見博客頁面。 感謝您提出的重大問題。

=============

假設您有Animal類, Mammal類, ReptileGiraffeTurtleTiger類,並且具有明顯的子類關系。

現在假設您有一個方法void M(ref Mammal m) M可以讀寫m


您可以將Animal類型的變量傳遞給M嗎?

否。該變量可以包含Turtle ,但是M會假定它僅包含哺乳動物。 Turtle不是Mammal

結論1 :不能將ref參數設置為“更大”。 (動物比哺乳動物多,因此該變量變得“更大”,因為它可以包含更多的東西。)


您可以將Giraffe類型的變量傳遞給M嗎?

M可以寫入m ,並且M可能想將Tiger寫入m 現在,您已經將Tiger放入了實際上是Giraffe類型的變量中。

結論2 :不能使ref參數“更小”。


現在考慮N(out Mammal n)

您可以將Giraffe類型的變量傳遞給N嗎?

N可以寫n ,而N可能想寫Tiger

結論3 :不能使out參數“更小”。


您可以將Animal類型的變量傳遞給N嗎?

好吧,為什么不呢? N無法從n讀取,只能對其進行寫入,對嗎? 您將Tiger寫入Animal類型的變量,就全部准備好了,對嗎?

錯誤。 規則不是“ N只能寫入n ”。

這些規則是:

1) N必須在N正常返回之前寫入n (如果N投擲,則所有投注均無效。)

2) N必須先向n寫入內容,然后才能從n讀取內容。

這允許發生以下一系列事件:

  • 聲明一個Animal類型的字段x
  • x作為out參數傳遞給N
  • NTiger寫入nnx的別名。
  • 在另一個線程上,有人將Turtle寫入x
  • N嘗試讀取n的內容,並在它認為是Mammal類型的變量中發現了Turtle

顯然,我們想使之非法。

結論4 :不能將out參數設為“更大”。


最終結論refout參數都不ref其類型。 否則將破壞可驗證的類型安全性。

如果您對基本類型理論中的這些問題感興趣,請考慮閱讀我的有關協方差和反方差如何在C#4.0中工作的系列文章

因為在兩種情況下,您都必須能夠將值分配給ref / out參數。

如果您嘗試將b傳遞給Foo2方法作為引用,並且在Foo2中嘗試使用a = new A(),則此方法無效。
不能寫的原因相同:

B b = new A();

您正在努力應對協方差 (和協方差 )的經典OOP問題,請參閱Wikipedia :盡管此事實可能違背了直觀的期望,但從數學上講,它不可能用派生類替代基類來代替可變的(可分配的)參數(並且也是出於相同原因可分配項目的容器),同時仍要遵守Liskov原則 在現有答案中概述了為什么這樣做,並在這些Wiki文章及其鏈接中進行了更深入的探討。

在保持傳統的靜態類型安全的同時,似乎這樣做的OOP語言卻在“作弊”(插入隱藏的動態類型檢查,或要求對所有源進行編譯時檢查)。 基本的選擇是:要么放棄這種協方差,接受從業者的困惑(就像C#在這里所做的那樣),要么轉向動態類型化方法(就像最早的OOP語言Smalltalk一樣),或者轉向不可變(單-數據),就像功能語言一樣(在不變性下,您可以支持協方差,還可以避免其他相關的難題,例如在可變數據世界中不能擁有Square子類Rectangle的事實)。

考慮:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

它將違反類型安全

盡管其他回答簡潔地解釋了此行為的原因,但我認為值得一提的是,如果您確實需要執行此類操作,則可以通過將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 {}  
}

因為給Foo2一個ref B會導致對象格式錯誤,因為Foo2只知道如何填充B A部分。

難道不是編譯器告訴您希望您顯式轉換對象,以確保您知道自己的意圖嗎?

Foo2(ref (A)b)

如果您對類型使用實際示例,則會看到以下內容:

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

您在SqlConnection的頂部填充了OracleConnection

就我而言,我的函數接受了一個對象,我什么也不能發送,所以我只是做了

object bla = myVar;
Foo(ref bla);

那行得通

我的Foo在VB.NET中,它檢查內部的類型並執行很多邏輯

如果我的回答是重復的,但其他人的回答太長,我深表歉意

從安全的角度講是有道理的,但是如果編譯器給出警告而不是錯誤,我會更喜歡它,因為通過引用傳遞的多態對象是合法使用的。 例如

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

這不會編譯,但是行得通嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM