Tại sao không 'ref' và 'out' hỗ trợ đa hình?


124

Thực hiện như sau:

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

Tại sao lỗi thời gian biên dịch ở trên xảy ra? Điều này xảy ra với cả hai refoutđối số.

Câu trả lời:


169

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

CẬP NHẬT: Tôi đã sử dụng câu trả lời này làm cơ sở cho mục blog này:

Tại sao tham số ref và out không cho phép biến thể kiểu?

Xem trang blog để biết thêm bình luận về vấn đề này. Cảm ơn vì câu hỏi tuyệt vời của bạn.

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

Giả sử bạn có các lớp học Animal, Mammal, Reptile, Giraffe, TurtleTiger, với các mối quan hệ subclassing rõ ràng.

Bây giờ giả sử bạn có một phương pháp void M(ref Mammal m). Mcả hai có thể đọc và ghi m.


Bạn có thể vượt qua một biến kiểu Animalđể M?

Không. Biến đó có thể chứa a Turtle, nhưng Msẽ cho rằng nó chỉ chứa Động vật có vú. A Turtlekhông phải là a Mammal.

Kết luận 1 : reftham số không thể được thực hiện "lớn hơn". (Có nhiều động vật hơn động vật có vú, vì vậy biến số ngày càng "lớn hơn" vì nó có thể chứa nhiều thứ hơn.)


Bạn có thể vượt qua một biến kiểu Giraffeđể M?

Số Mcó thể viết mMcó thể muốn viết Tigervào m. Bây giờ bạn đã đặt Tigermột biến thực sự là loại Giraffe.

Kết luận 2 : reftham số không thể được thực hiện "nhỏ hơn".


Bây giờ hãy xem xét N(out Mammal n).

Bạn có thể vượt qua một biến kiểu Giraffeđể N?

Số Ncó thể viết nNcó thể muốn viết a Tiger.

Kết luận 3 : outtham số không thể được thực hiện "nhỏ hơn".


Bạn có thể vượt qua một biến kiểu Animalđể N?

Hừm.

Cũng tại sao không? NKhông thể đọc từ n, nó chỉ có thể viết cho nó, phải không? Bạn viết một Tigerbiến cho một loại Animalvà bạn đã hoàn tất, phải không?

Sai lầm. Quy tắc không phải là " Nchỉ có thể ghi vào n".

Các quy tắc là, ngắn gọn:

1) Nphải ghi vào ntrước khi Ntrở lại bình thường. (Nếu Nném, tất cả các cược đã tắt.)

2) Nphải viết một cái gì đó ntrước khi nó đọc một cái gì đó từ n.

Điều đó cho phép chuỗi sự kiện này:

  • Khai báo một lĩnh vực xloại Animal.
  • Truyền xnhư một outtham số cho N.
  • Nviết một Tigervào n, đó là một bí danh cho x.
  • Trên một chủ đề khác, ai đó viết một Turtlevào x.
  • Ncố gắng đọc nội dung của nvà phát hiện ra một Turtletrong những gì nó nghĩ là một biến loại Mammal.

Rõ ràng chúng tôi muốn làm cho bất hợp pháp.

Kết luận 4 : outtham số không thể được thực hiện "lớn hơn".


Kết luận cuối cùng : Cả tham số refcũng không outthể thay đổi loại của chúng. Để làm khác là phá vỡ sự an toàn loại có thể kiểm chứng.

Nếu những vấn đề này trong lý thuyết loại cơ bản khiến bạn quan tâm, hãy xem xét việc đọc loạt bài của tôi về cách hiệp phương sai và chống đối tác hoạt động trong C # 4.0 .


6
+1. Giải thích tuyệt vời bằng cách sử dụng các ví dụ trong thế giới thực thể hiện rõ ràng các vấn đề (nghĩa là - giải thích với A, B và C làm cho việc chứng minh tại sao nó không hoạt động khó khăn hơn).
Cấp Wagner

4
Tôi cảm thấy khiêm tốn khi đọc quá trình suy nghĩ này. Tôi nghĩ rằng tôi nên trở lại với những cuốn sách!
Scott McKenzie

Trong trường hợp này, chúng tôi thực sự không thể sử dụng biến lớp Trừu tượng làm đối số và truyền vào đối tượng lớp dẫn xuất của nó !!
Cholachagudda Prashant

Tuy nhiên, tại sao outcác tham số không thể được thực hiện "lớn hơn"? Trình tự bạn đã mô tả có thể được áp dụng cho bất kỳ biến nào, không chỉ outbiến tham số. Và người đọc cũng cần truyền giá trị đối số Mammaltrước khi anh ta cố gắng truy cập nó Mammalvà dĩ nhiên nó có thể thất bại nếu anh ta không quan tâm
astef

29

Bởi vì trong cả hai trường hợp, bạn phải có khả năng gán giá trị cho tham số ref / out.

Nếu bạn cố gắng chuyển b vào phương thức Foo2 làm tham chiếu và trong Foo2, bạn cố gắng khẳng định a = new A (), điều này sẽ không hợp lệ.
Lý do tương tự bạn không thể viết:

B b = new A();

+1 Đi thẳng vào vấn đề và giải thích hoàn toàn chính xác lý do.
Rui Craveiro

10

Bạn đang vật lộn với vấn đề OOP cổ điển về hiệp phương sai (và chống chỉ định), xem wikipedia : vì thực tế này có thể bất chấp những kỳ vọng trực quan, về mặt toán học không thể cho phép thay thế các lớp dẫn xuất thay cho các lớp cơ sở cho các đối số có thể thay đổi (có thể gán) cũng có các container có mục được gán, vì lý do tương tự) trong khi vẫn tôn trọng nguyên tắc của Liskov . Tại sao điều đó lại được phác họa trong các câu trả lời hiện có và khám phá sâu hơn trong các bài viết và liên kết wiki này từ đó.

Các ngôn ngữ OOP dường như làm như vậy trong khi vẫn giữ nguyên kiểu truyền thống tĩnh là "gian lận" (chèn kiểm tra loại động ẩn hoặc yêu cầu kiểm tra thời gian biên dịch của TẤT CẢ các nguồn để kiểm tra); lựa chọn cơ bản là: từ bỏ hiệp phương sai này và chấp nhận sự bối rối của các học viên (như C # ở đây), hoặc chuyển sang một cách tiếp cận gõ động (như ngôn ngữ OOP đầu tiên, Smalltalk, đã làm), hoặc chuyển sang bất biến (đơn gán) dữ liệu, giống như các ngôn ngữ chức năng thực hiện (dưới sự bất biến, bạn có thể hỗ trợ hiệp phương sai và cũng tránh các câu đố liên quan khác như thực tế là bạn không thể có Hình chữ nhật lớp con vuông trong thế giới dữ liệu có thể thay đổi).


4

Xem xét:

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

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

B b = null;
Foo2(ref b);

Nó sẽ vi phạm an toàn loại


Đó là loại "b" được suy luận không rõ ràng do var đó là vấn đề ở đó.

Tôi đoán trên dòng 6 bạn có nghĩa là => B b = null;
Alejandro Miralles

@amiralles - vâng, điều đó varhoàn toàn sai. Đã sửa.
Henk Holterman

4

Mặc dù các phản hồi khác đã giải thích ngắn gọn lý do đằng sau hành vi này, tôi nghĩ rằng đáng nói rằng nếu bạn thực sự cần phải làm một cái gì đó có tính chất này, bạn có thể thực hiện chức năng tương tự bằng cách biến Foo2 thành một phương pháp chung, như vậy:

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

2

Bởi vì đưa ra Foo2một ref Bkết quả sẽ dẫn đến một đối tượng không đúng định dạng vì Foo2chỉ biết làm thế nào để điền vào Amột phần của B.


0

Không phải là trình biên dịch nói với bạn rằng nó muốn bạn phân tách rõ ràng đối tượng để nó có thể chắc chắn rằng bạn biết ý định của bạn là gì?

Foo2(ref (A)b)

Không thể làm điều đó, "Đối số ref hoặc out phải là biến có thể gán"

0

Có ý nghĩa từ góc độ an toàn, nhưng tôi sẽ thích nó hơn nếu trình biên dịch đưa ra cảnh báo thay vì lỗi, vì có những cách sử dụng hợp pháp các đối tượng polymoprhic được truyền qua tham chiếu. ví dụ

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

Điều này sẽ không được biên dịch, nhưng nó sẽ làm việc?


0

Nếu bạn sử dụng các ví dụ thực tế cho các loại của mình, bạn sẽ thấy nó:

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

Và bây giờ bạn có chức năng của bạn có tổ tiên ( tức là Object ):

void Foo2(ref Object connection) { }

Điều gì có thể có thể sai với điều đó?

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

Bạn chỉ cần quản lý để gán một Bitmapcho bạn SqlConnection.

Điêu đo không tôt.


Thử lại với người khác:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

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

Bạn nhồi một cái OracleConnectiontrên đầu của bạn SqlConnection.


0

Trong trường hợp của tôi, chức năng của tôi đã chấp nhận một đối tượng và tôi không thể gửi bất cứ điều gì vì vậy tôi chỉ đơn giản là làm

object bla = myVar;
Foo(ref bla);

Và nó hoạt động

Foo của tôi đang ở trong VB.NET và nó kiểm tra kiểu bên trong và thực hiện nhiều logic

Tôi xin lỗi nếu câu trả lời của tôi trùng lặp nhưng những câu khác quá dài

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.