Làm thế nào để ValueTypes bắt nguồn từ Object (ReferenceType) và vẫn là ValueTypes?


83

C # không cho phép cấu trúc dẫn xuất từ ​​các lớp, nhưng tất cả các ValueTypes đều bắt nguồn từ Object. Sự phân biệt này được thực hiện ở đâu?

CLR xử lý điều này như thế nào?


Kết quả của ma thuật đen của System.ValueTypeloại trong hệ thống loại CLR.
RBT

Câu trả lời:


107

C # không cho phép các cấu trúc dẫn xuất từ ​​các lớp

Tuyên bố của bạn không chính xác, do đó bạn nhầm lẫn. C # không cho phép các cấu trúc dẫn xuất từ ​​các lớp. Tất cả các cấu trúc bắt nguồn từ cùng một lớp, System.ValueType, bắt nguồn từ System.Object. Và tất cả các enum đều bắt nguồn từ System.Enum.

CẬP NHẬT: Đã có một số nhầm lẫn trong một số nhận xét (hiện đã bị xóa), cần được làm rõ. Tôi sẽ hỏi thêm một số câu hỏi:

Các cấu trúc có bắt nguồn từ một kiểu cơ sở không?

Rõ ràng là có. Chúng ta có thể thấy điều này bằng cách đọc trang đầu tiên của thông số kỹ thuật:

Tất cả các kiểu C #, bao gồm các kiểu nguyên thủy như int và double, đều kế thừa từ một kiểu đối tượng gốc duy nhất.

Bây giờ, tôi lưu ý rằng đặc tả đã phóng đại trường hợp ở đây. Các kiểu con trỏ không bắt nguồn từ đối tượng và mối quan hệ dẫn xuất cho các kiểu giao diện và kiểu tham số kiểu phức tạp hơn so với bản phác thảo này. Tuy nhiên, rõ ràng là trường hợp tất cả các kiểu cấu trúc đều bắt nguồn từ một kiểu cơ sở.

Có những cách nào khác mà chúng ta biết rằng kiểu cấu trúc bắt nguồn từ kiểu cơ sở không?

Chắc chắn rồi. Một loại cấu trúc có thể ghi đè ToString. Nó là gì, nếu không phải là một phương thức ảo của kiểu cơ sở của nó? Do đó nó phải có một loại cơ sở. Loại cơ sở đó là một lớp.

Tôi có thể lấy cấu trúc do người dùng xác định từ một lớp mà tôi chọn không?

Hoàn toàn không. Điều này không có nghĩa là các cấu trúc không bắt nguồn từ một lớp . Các cấu trúc bắt nguồn từ một lớp và do đó kế thừa các thành viên kế thừa của lớp đó. Trên thực tế, các cấu trúc được yêu cầu để dẫn xuất từ ​​một lớp cụ thể: Các enzim được yêu cầu để dẫn xuất từ ​​đó Enum, các cấu trúc được yêu cầu để dẫn xuất từ ​​đó ValueType. Bởi vì những điều này là bắt buộc , ngôn ngữ C # cấm bạn nêu mối quan hệ dẫn xuất trong mã.

Tại sao lại cấm nó?

Khi một mối quan hệ được yêu cầu , trình thiết kế ngôn ngữ có các tùy chọn: (1) yêu cầu người dùng nhập câu chú bắt buộc, (2) làm cho nó tùy chọn hoặc (3) cấm nó. Mỗi ngôn ngữ đều có ưu và nhược điểm, và các nhà thiết kế ngôn ngữ C # đã lựa chọn khác nhau tùy thuộc vào chi tiết cụ thể của từng ngôn ngữ.

Ví dụ, các trường const bắt buộc phải tĩnh, nhưng không được phép nói rằng chúng như vậy bởi vì làm như vậy là trường hợp đầu tiên, vô nghĩa và thứ hai, ngụ ý rằng có các trường const không tĩnh. Nhưng các toán tử quá tải được yêu cầu phải được đánh dấu là tĩnh, mặc dù nhà phát triển không có lựa chọn nào khác; quá dễ dàng để các nhà phát triển tin rằng quá tải toán tử là một phương thức thể hiện khác. Điều này khắc phục lo ngại rằng người dùng có thể tin rằng "tĩnh" ngụ ý rằng, "ảo" cũng là một khả năng.

Trong trường hợp này, việc yêu cầu người dùng nói rằng cấu trúc của họ bắt nguồn từ ValueType có vẻ giống như một đoạn văn thừa và điều đó ngụ ý rằng cấu trúc có thể bắt nguồn từ một kiểu khác. Để loại bỏ cả hai vấn đề này, C # làm cho nó bất hợp pháp khi tuyên bố trong mã rằng một cấu trúc bắt nguồn từ một kiểu cơ sở, mặc dù rõ ràng nó có.

Tương tự như vậy, tất cả các kiểu ủy nhiệm đều bắt nguồn từ MulticastDelegate, nhưng C # yêu cầu bạn không nói điều đó.

Vì vậy, bây giờ chúng ta đã thiết lập rằng tất cả các cấu trúc trong C # đều bắt nguồn từ một lớp .

Mối quan hệ giữa kế thừadẫn xuất từ ​​một lớp là gì?

Nhiều người bối rối bởi mối quan hệ kế thừa trong C #. Mối quan hệ kế thừa khá đơn giản: nếu kiểu struct, lớp hoặc kiểu ủy nhiệm D bắt nguồn từ kiểu lớp B thì các thành viên kế thừa của B cũng là thành viên của D. Nó đơn giản như vậy.

Điều đó có nghĩa gì đối với sự kế thừa khi chúng ta nói rằng một cấu trúc bắt nguồn từ ValueType? Đơn giản là tất cả các thành viên kế thừa của ValueType cũng là thành viên của cấu trúc. Đây là cách các cấu trúc có được việc triển khai ToString, chẳng hạn như; nó được kế thừa từ lớp cơ sở của struct.

Tất cả các thành viên thừa kế? Chắc chắn là không. Các thành viên tư nhân có di truyền không?

Đúng. Tất cả các thành viên riêng của một lớp cơ sở cũng là thành viên của kiểu dẫn xuất. Tất nhiên, việc gọi tên các thành viên đó là bất hợp pháp nếu trang web gọi điện không nằm trong miền khả năng truy cập của thành viên đó. Chỉ vì bạn có một thành viên không có nghĩa là bạn có thể sử dụng nó!

Bây giờ chúng ta tiếp tục với câu trả lời ban đầu:


CLR xử lý điều này như thế nào?

Cực kỳ tốt. :-)

Điều làm cho một kiểu giá trị trở thành một kiểu giá trị là các thể hiện của nó được sao chép theo giá trị . Điều làm cho một kiểu tham chiếu trở thành một kiểu tham chiếu là các thể hiện của nó được sao chép bằng tham chiếu . Bạn dường như có một số niềm tin rằng mối quan hệ kế thừa giữa các loại giá trị và các loại tham chiếu bằng cách nào đó đặc biệt và bất thường, nhưng tôi không hiểu niềm tin đó là gì. Kế thừa không liên quan gì đến cách mọi thứ được sao chép.

Nhìn nó theo cách này. Giả sử tôi đã nói với bạn những sự kiện sau:

  • Có hai loại hộp, hộp màu đỏ và hộp màu xanh.

  • Mọi ô màu đỏ đều trống.

  • Có ba hộp màu xanh đặc biệt được gọi là O, V và E.

  • O không ở bên trong bất kỳ hộp nào.

  • V nằm trong O.

  • E ở bên trong V.

  • Không có hộp màu xanh nào khác bên trong V.

  • Không có ô màu xanh bên trong E.

  • Mỗi ô màu đỏ đều ở dạng V hoặc E.

  • Mỗi hộp màu xanh khác với O đều nằm trong một hộp màu xanh lam.

Các hộp màu xanh là kiểu tham chiếu, hộp màu đỏ là kiểu giá trị, O là System.Object, V là System.ValueType, E là System.Enum và mối quan hệ "bên trong" là "dẫn xuất từ".

Đó là một bộ quy tắc hoàn toàn nhất quán và đơn giản mà bạn có thể dễ dàng tự thực hiện, nếu bạn có nhiều bìa cứng và nhiều kiên nhẫn. Cho dù một chiếc hộp màu đỏ hay xanh lam không liên quan gì đến những gì bên trong nó; trong thế giới thực, hoàn toàn có thể đặt một hộp màu đỏ vào trong một hộp màu xanh. Trong CLR, hoàn toàn hợp pháp khi tạo kiểu giá trị kế thừa từ kiểu tham chiếu, miễn là nó là System.ValueType hoặc System.Enum.

Vì vậy, hãy diễn đạt lại câu hỏi của bạn:

Làm thế nào để ValueTypes bắt nguồn từ Object (ReferenceType) và vẫn là ValueTypes?

như

Làm thế nào để mọi hộp màu đỏ (các kiểu giá trị) đều nằm bên trong (xuất phát từ) hộp O (System.Object), là hộp màu xanh lam (Kiểu tham chiếu) và vẫn là hộp màu đỏ (kiểu giá trị)?

Khi bạn diễn đạt như vậy, tôi hy vọng nó hiển nhiên. Không có gì ngăn cản bạn đặt một hộp màu đỏ bên trong hộp V, bên trong hộp O, màu xanh lam. Tại sao sẽ có?


CẬP NHẬT BỔ SUNG:

Câu hỏi ban đầu của Joan là về cách nó có thểmà một kiểu giá trị dẫn xuất từ ​​một kiểu tham chiếu. Câu trả lời ban đầu của tôi không thực sự giải thích bất kỳ cơ chế nào mà CLR sử dụng để giải thích thực tế là chúng ta có mối quan hệ dẫn xuất giữa hai thứ có các biểu diễn hoàn toàn khác nhau - cụ thể là liệu dữ liệu được tham chiếu có tiêu đề đối tượng hay không, khối đồng bộ, cho dù nó sở hữu bộ nhớ riêng cho mục đích thu gom rác, v.v. Những cơ chế này phức tạp, quá phức tạp để giải thích trong một câu trả lời. Các quy tắc của hệ thống kiểu CLR khá phức tạp hơn một chút so với hương vị hơi đơn giản của nó mà chúng ta thấy trong C #, chẳng hạn như không có sự phân biệt rõ ràng giữa các phiên bản đóng hộp và không hộp của một loại. Sự ra đời của generic cũng gây ra nhiều phức tạp bổ sung được thêm vào CLR.


8
Cấu trúc ngôn ngữ phải có ý nghĩa. Điều đó có nghĩa là gì khi có một kiểu giá trị tùy ý bắt nguồn từ một kiểu tham chiếu tùy ý? Có điều gì bạn có thể thực hiện được với một kế hoạch mà bạn cũng không thể thực hiện được với các chuyển đổi ngầm định do người dùng xác định không?
Eric Lippert

2
Tôi đoán là không. Tôi chỉ nghĩ rằng bạn có thể có một số thành viên có sẵn cho nhiều loại giá trị mà bạn xem như một nhóm, bạn có thể làm điều này bằng cách sử dụng một lớp trừu tượng để lấy ra cấu trúc. Tôi đoán bạn có thể sử dụng chuyển đổi ngầm định nhưng sau đó bạn sẽ phải trả tiền phạt về hiệu suất, phải không? Nếu bạn đang làm hàng triệu người trong số họ.
Joan Venge

8
Ah tôi thấy. Bạn muốn sử dụng kế thừa không phải như một cơ chế để mô hình hóa các mối quan hệ "là một loại", mà chỉ đơn giản là một cơ chế để chia sẻ mã giữa một loạt các kiểu liên quan. Đó có vẻ là một kịch bản hợp lý, mặc dù cá nhân tôi cố gắng tránh sử dụng kế thừa hoàn toàn như một tiện ích chia sẻ mã.
Eric Lippert

3
Joan để xác định hành vi một lần, bạn có thể tạo một giao diện, có cấu trúc bạn muốn chia sẻ hành vi thực hiện giao diện sau đó tạo một phương thức mở rộng hoạt động trên giao diện. Một vấn đề tiềm ẩn với cách tiếp cận này là khi gọi các phương thức giao diện, cấu trúc sẽ được đóng hộp trước và giá trị được đóng hộp được sao chép sẽ được chuyển đến phương thức mở rộng. Mọi thay đổi về trạng thái sẽ xảy ra trên bản sao của đối tượng có thể không trực quan đối với người dùng API.
David Silva Smith

2
@Sipo: Công bằng mà nói, câu hỏi bao gồm "CLR xử lý việc này như thế nào?" và câu trả lời thực hiện tốt việc mô tả cách CLR thực hiện các quy tắc này. Nhưng đây là điều: chúng ta nên mong đợi rằng hệ thống triển khai một ngôn ngữ không có các quy tắc giống như ngôn ngữ đó! Các hệ thống thực thi nhất thiết phải là cấp thấp hơn, nhưng đừng nhầm lẫn các quy tắc của hệ thống cấp thấp hơn với các quy tắc của hệ thống cấp cao được xây dựng trên nó. Chắc chắn, hệ thống loại CLR phân biệt giữa các loại giá trị được đóng hộp và không được đóng hộp, như tôi đã lưu ý trong câu trả lời của mình. Nhưng C # thì không .
Eric Lippert

21

Điều chỉnh nhỏ, C # không cho phép các cấu trúc tùy chỉnh bắt nguồn từ bất kỳ thứ gì, không chỉ các lớp. Tất cả những gì một cấu trúc có thể làm là triển khai một giao diện rất khác với giao diện dẫn xuất.

Tôi nghĩ cách tốt nhất để trả lời điều này là điều đó ValueTypethật đặc biệt. Về cơ bản nó là lớp cơ sở cho tất cả các kiểu giá trị trong hệ thống kiểu CLR. Thật khó để biết cách trả lời "làm thế nào để CLR xử lý điều này" bởi vì nó đơn giản là một quy tắc của CLR.


+1 cho điểm tốt về cấu trúc không bắt nguồn từ bất kỳ thứ gì [ngoại trừ hoàn toàn bắt nguồn từ System.ValueType].
Reed Copsey

2
Bạn nói rằng điều đó ValueTypelà đặc biệt, nhưng điều đáng nói là ValueTypenó thực sự là một kiểu tham chiếu.
LukeH

Nếu nội bộ cấu trúc có thể bắt nguồn từ một lớp, tại sao họ không hiển thị nó cho mọi người?
Joan Venge

1
@Joan: Thực sự thì không. Điều này chỉ để bạn có thể truyền một cấu trúc đến một đối tượng và ở đó để sử dụng. Nhưng về mặt kỹ thuật, khi so sánh với cách các lớp được triển khai, các kiểu giá trị được CLR xử lý hoàn toàn khác.
Reed Copsey

2
@JoanVenge Tôi tin rằng sự nhầm lẫn ở đây là nói rằng cấu trúc bắt nguồn từ lớp ValueType trong CLR. Tôi tin rằng đúng hơn khi nói rằng trong CLR, các cấu trúc không thực sự tồn tại, việc triển khai "struct" trong CLR thực sự là lớp ValueType. Vì vậy, nó không giống như một cấu trúc được kế thừa từ ValueType trong CLR.
wired_in

20

Đây là một cấu trúc hơi nhân tạo được CLR duy trì để cho phép tất cả các loại được coi như một System.Object.

Các kiểu giá trị bắt nguồn từ System.Object thông qua System.ValueType , là nơi xử lý đặc biệt xảy ra (ví dụ: CLR xử lý quyền anh / mở hộp, v.v. đối với bất kỳ kiểu nào bắt nguồn từ ValueType).


6

Tuyên bố của bạn không chính xác, do đó bạn nhầm lẫn. C # không cho phép các cấu trúc dẫn xuất từ ​​các lớp. Tất cả các cấu trúc bắt nguồn từ cùng một lớp, System.ValueType

Vì vậy, hãy thử điều này:

 struct MyStruct :  System.ValueType
 {
 }

Điều này thậm chí sẽ không biên dịch. Trình biên dịch sẽ nhắc bạn "Gõ 'System.ValueType' trong danh sách giao diện không phải là giao diện".

Khi dịch ngược Int32 là một cấu trúc, bạn sẽ thấy:

public struct Int32: IComp so sánh được, IFormattable, IConvertible {}, không đề cập đến nó có nguồn gốc từ System.ValueType. Nhưng trong trình duyệt đối tượng, bạn thấy Int32 kế thừa từ System.ValueType.

Vì vậy, tất cả những điều này khiến tôi tin rằng:

Tôi nghĩ rằng cách tốt nhất để trả lời điều này là ValueType là đặc biệt. Về cơ bản, nó là lớp cơ sở cho tất cả các kiểu giá trị trong hệ thống kiểu CLR. Thật khó để biết cách trả lời "làm thế nào để CLR xử lý điều này" bởi vì nó đơn giản là một quy tắc của CLR.


Các cấu trúc dữ liệu tương tự được sử dụng trong .NET để mô tả nội dung của kiểu giá trị và kiểu tham chiếu, nhưng khi CLR nhìn thấy định nghĩa kiểu được xác định là dẫn xuất từ ValueTypeđó, nó sẽ sử dụng định nghĩa đó để xác định hai loại đối tượng: kiểu đối tượng đống hoạt động như kiểu tham chiếu và kiểu vị trí lưu trữ nằm ngoài hệ thống kế thừa kiểu. Bởi vì hai loại thứ đó được sử dụng trong các ngữ cảnh loại trừ lẫn nhau, nên các bộ mô tả cùng loại có thể được sử dụng cho cả hai. Ở cấp CLR, một cấu trúc được định nghĩa là lớp có cha là mẹ System.ValueType, nhưng C # ...
supercat

... cấm chỉ định rằng cấu trúc kế thừa từ bất kỳ thứ gì vì chỉ có một thứ mà chúng có thể kế thừa từ ( System.ValueType) và cấm các lớp chỉ định rằng chúng kế thừa từ đó System.ValueTypevì bất kỳ lớp nào được khai báo theo cách đó sẽ hoạt động giống như một kiểu giá trị.
supercat

3

Một loại giá trị đóng hộp thực sự là một loại tham chiếu (nó đi như một và quacks như một, vì vậy hiệu quả nó là một). Tôi đề nghị rằng ValueType không thực sự là kiểu cơ sở của các kiểu giá trị, mà là kiểu tham chiếu cơ sở mà các kiểu giá trị có thể được chuyển đổi khi ép kiểu Đối tượng. Bản thân các kiểu giá trị không đóng hộp nằm ngoài hệ thống phân cấp đối tượng.


1
Tôi nghĩ rằng bạn có nghĩa là, "ValueType là không thực sự là kiểu cơ sở của giá trị các loại"
wired_in

1
@wired_in: Cảm ơn. Đã sửa.
supercat

2

Cơ sở lý luận

Trong tất cả các câu trả lời, câu trả lời của @ supercat gần nhất với câu trả lời thực tế. Vì các câu trả lời khác không thực sự trả lời câu hỏi và hoàn toàn đưa ra tuyên bố không chính xác (ví dụ: các loại giá trị kế thừa từ bất kỳ thứ gì), tôi quyết định trả lời câu hỏi.

 

Lời mở đầu

Câu trả lời này dựa trên kỹ thuật đảo ngược của riêng tôi và đặc điểm kỹ thuật CLI.

structclasslà các từ khóa C #. Theo như CLI có liên quan, tất cả các kiểu (lớp, giao diện, cấu trúc, v.v.) được xác định bởi các định nghĩa lớp.

Ví dụ, một loại đối tượng (Được biết đến trong C # là class) được định nghĩa như sau:

.class MyClass
{
}

 

Một giao diện được xác định bởi một định nghĩa lớp với interfacethuộc tính ngữ nghĩa:

.class interface MyInterface
{
}

 

Còn các loại giá trị thì sao?

Lý do mà các cấu trúc có thể kế thừa System.ValueTypevà vẫn là các kiểu giá trị, là bởi vì .. chúng không.

Kiểu giá trị là cấu trúc dữ liệu đơn giản. Các kiểu giá trị không kế thừa từ bất cứ thứ gì và chúng không thể triển khai các giao diện. Các kiểu giá trị không phải là kiểu con của bất kỳ kiểu nào và chúng không có bất kỳ thông tin kiểu nào. Với một địa chỉ bộ nhớ của một kiểu giá trị, không thể xác định kiểu giá trị đại diện cho cái gì, không giống như kiểu tham chiếu có thông tin kiểu trong một trường ẩn.

Nếu chúng ta hình dung cấu trúc C # sau:

namespace MyNamespace
{
    struct MyValueType : ICloneable
    {
        public int A;
        public int B;
        public int C;

        public object Clone()
        {
            // body omitted
        }
    }
}

Sau đây là định nghĩa lớp IL của struct đó:

.class MyNamespace.MyValueType extends [mscorlib]System.ValueType implements [mscorlib]System.ICloneable
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method public final hidebysig newslot virtual instance object Clone() cil managed
    {
        // body omitted
    }
}

Vậy điều gì đang xảy ra ở đây? Nó mở rộng rõ ràng System.ValueType, là một kiểu đối tượng / tham chiếu, thực hiện System.ICloneable.

Giải thích là, khi định nghĩa lớp mở rộng System.ValueTypenó thực sự xác định 2 thứ: Kiểu giá trị và kiểu đóng hộp tương ứng của kiểu giá trị. Các thành viên của định nghĩa lớp xác định biểu diễn cho cả kiểu giá trị và kiểu hộp tương ứng. Nó không phải là kiểu giá trị mở rộng và triển khai, nó là kiểu đóng hộp tương ứng. Các extendsimplementstừ khóa chỉ áp dụng đối với các loại đóng hộp.

Để làm rõ, định nghĩa lớp ở trên thực hiện 2 điều:

  1. Xác định một kiểu giá trị với 3 trường (Và một phương thức). Nó không kế thừa từ bất kỳ thứ gì và nó không triển khai bất kỳ giao diện nào (các kiểu giá trị cũng không thể làm được).
  2. Xác định một kiểu đối tượng (kiểu đóng hộp) với 3 trường (Và triển khai một phương thức giao diện), kế thừa System.ValueTypevà triển khai System.ICloneablegiao diện.

Cũng lưu ý rằng bất kỳ định nghĩa lớp nào mở rộng System.ValueTypevề bản chất cũng được đóng dấu, cho dù sealedtừ khóa có được chỉ định hay không.

Vì các kiểu giá trị chỉ là những cấu trúc đơn giản, không kế thừa, không triển khai và không hỗ trợ tính đa hình, chúng không thể được sử dụng với phần còn lại của hệ thống kiểu. Để giải quyết vấn đề này, ngoài kiểu giá trị, CLR còn xác định kiểu tham chiếu tương ứng với các trường giống nhau, được gọi là kiểu đóng hộp. Vì vậy, trong khi một kiểu giá trị không thể được chuyển cho các phương thức lấy dấu object, thì kiểu đóng hộp tương ứng của nó có thể .

 

Bây giờ, nếu bạn định nghĩa một phương thức trong C # như

public static void BlaBla(MyNamespace.MyValueType x),

bạn biết rằng phương thức sẽ nhận kiểu giá trị MyNamespace.MyValueType.

Ở trên, chúng ta đã biết rằng định nghĩa lớp là kết quả của từ structkhóa trong C # thực sự xác định cả kiểu giá trị và kiểu đối tượng. Tuy nhiên, chúng tôi chỉ có thể tham chiếu đến kiểu giá trị đã xác định. Mặc dù đặc tả CLI nói rằng từ khóa ràng buộc boxedcó thể được sử dụng để tham chiếu đến phiên bản đóng hộp của một loại, từ khóa này không tồn tại (Xem ECMA-335, II.13.1 Tham chiếu các loại giá trị). Nhưng hãy tưởng tượng điều đó xảy ra trong giây lát.

Khi tham chiếu đến các loại trong IL, một số ràng buộc được hỗ trợ, trong số đó có classvaluetype. Nếu chúng ta sử dụng, valuetype MyNamespace.MyTypechúng ta đang chỉ định định nghĩa lớp kiểu giá trị được gọi là MyNamespace.MyType. Tương tự như vậy, chúng ta có thể sử dụng class MyNamespace.MyTypeđể chỉ định định nghĩa lớp kiểu đối tượng được gọi là MyNamespace.MyType. Có nghĩa là trong IL bạn có thể có một kiểu giá trị (struct) và một kiểu đối tượng (lớp) có cùng tên và vẫn phân biệt được chúng. Bây giờ, nếu boxedtừ khóa được ghi chú bởi đặc tả CLI thực sự được triển khai, chúng ta có thể sử dụng boxed MyNamespace.MyTypeđể chỉ định kiểu đóng hộp của định nghĩa lớp kiểu giá trị được gọi là MyNamespace.MyType.

Vì vậy, .method static void Print(valuetype MyNamespace.MyType test) cil managedlấy kiểu giá trị được xác định bởi định nghĩa lớp kiểu giá trị có tên MyNamespace.MyType,

while .method static void Print(class MyNamespace.MyType test) cil managedlấy kiểu đối tượng được định nghĩa bởi định nghĩa lớp kiểu đối tượng được đặt tên MyNamespace.MyType.

tương tự như vậy nếu boxedlà một từ khóa, .method static void Print(boxed MyNamespace.MyType test) cil managedsẽ lấy kiểu đóng hộp của kiểu giá trị được xác định bởi định nghĩa lớp có tên MyNamespace.MyType.

Sau đó bạn sẽ có thể nhanh chóng loại đóng hộp giống như bất kỳ loại đối tượng khác và vượt qua nó xung quanh để bất kỳ phương pháp mà phải mất một System.ValueType, objecthoặc boxed MyNamespace.MyValueTypenhư một cuộc tranh cãi, và nó sẽ, đối với tất cả các tính năng, hoạt động giống như bất kỳ loại tài liệu tham khảo khác. Nó KHÔNG phải là một kiểu giá trị, mà là kiểu đóng hộp tương ứng của một kiểu giá trị.

 

Tóm lược

Vì vậy, tóm lại, và để trả lời câu hỏi:

Các kiểu giá trị không phải là kiểu tham chiếu và không kế thừa từ System.ValueTypehoặc bất kỳ kiểu nào khác, và chúng không thể triển khai các giao diện. Các kiểu đóng hộp tương ứng cũng được xác định sẽ kế thừa System.ValueTypecó thể triển khai các giao diện.

Một .classđịnh nghĩa xác định những thứ khác nhau tùy thuộc vào hoàn cảnh.

  • Nếu interfacethuộc tính ngữ nghĩa được chỉ định, định nghĩa lớp xác định một giao diện.
  • Nếu interfacethuộc tính ngữ nghĩa không được chỉ định, và định nghĩa không mở rộng System.ValueType, định nghĩa lớp xác định một kiểu đối tượng (lớp).
  • Nếu interfacethuộc tính ngữ nghĩa không được xác định, và định nghĩa không mở rộng System.ValueType, định nghĩa lớp định nghĩa một kiểu giá trị loại đóng hộp tương ứng của nó (struct).

Bố cục bộ nhớ

Phần này giả định một quy trình 32 bit

Như đã đề cập, các kiểu giá trị không có thông tin về kiểu, và do đó không thể xác định kiểu giá trị đại diện từ vị trí bộ nhớ của nó. Một cấu trúc mô tả một kiểu dữ liệu đơn giản và chỉ chứa các trường mà nó xác định:

public struct MyStruct
{
    public int A;
    public short B;
    public int C;
}

Nếu chúng ta tưởng tượng rằng một phiên bản của MyStruct được cấp phát tại địa chỉ 0x1000, thì đây là bố cục bộ nhớ:

0x1000: int A;
0x1004: short B;
0x1006: 2 byte padding
0x1008: int C;

Các cấu trúc mặc định là bố cục tuần tự. Các trường được căn chỉnh trên các ranh giới có kích thước riêng. Lớp đệm được thêm vào để đáp ứng điều này.

 

Nếu chúng ta định nghĩa một lớp theo cùng một cách, như:

public class MyClass
{
    public int A;
    public short B;
    public int C;
}

Tưởng tượng cùng một địa chỉ, bố cục bộ nhớ như sau:

0x1000: Pointer to object header
0x1004: int A;
0x1008: int C;
0x100C: short B;
0x100E: 2 byte padding
0x1010: 4 bytes extra

Các lớp được mặc định là bố cục tự động và trình biên dịch JIT sẽ sắp xếp chúng theo thứ tự tối ưu nhất. Các trường được căn chỉnh trên các ranh giới có kích thước riêng. Lớp đệm được thêm vào để đáp ứng điều này. Tôi không chắc tại sao, nhưng mỗi lớp luôn có thêm 4 byte ở cuối.

Offset 0 chứa địa chỉ của tiêu đề đối tượng, chứa thông tin kiểu, bảng phương thức ảo, v.v. Điều này cho phép thời gian chạy xác định dữ liệu tại một địa chỉ đại diện, không giống như các kiểu giá trị.

Do đó, các kiểu giá trị không hỗ trợ tính kế thừa, giao diện cũng như tính đa hình.

Phương pháp

Các kiểu giá trị không có bảng phương thức ảo và do đó không hỗ trợ tính đa hình. Tuy nhiên , loại đóng hộp tương ứng của họ không .

Khi bạn có một phiên bản của cấu trúc và cố gắng gọi một phương thức ảo như ToString()được định nghĩa trên System.Object, thời gian chạy phải đóng hộp cấu trúc.

MyStruct myStruct = new MyStruct();
Console.WriteLine(myStruct.ToString()); // ToString() call causes boxing of MyStruct.

Tuy nhiên, nếu cấu trúc ghi đè ToString()thì lệnh gọi sẽ bị ràng buộc tĩnh và thời gian chạy sẽ gọi MyStruct.ToString()mà không có quyền truy cập và không cần tìm trong bất kỳ bảng phương thức ảo nào (cấu trúc không có bất kỳ). Vì lý do này, nó cũng có thể nội tuyến ToString()cuộc gọi.

Nếu cấu trúc ghi đè ToString()và được đóng hộp, thì cuộc gọi sẽ được giải quyết bằng cách sử dụng bảng phương thức ảo.

System.ValueType myStruct = new MyStruct(); // Creates a new instance of the boxed type of MyStruct.
Console.WriteLine(myStruct.ToString()); // ToString() is now called through the virtual method table.

Tuy nhiên, hãy nhớ rằng nó ToString()được định nghĩa trong struct và do đó hoạt động trên giá trị struct, vì vậy nó mong đợi một kiểu giá trị. Kiểu đóng hộp, giống như bất kỳ lớp nào khác, có tiêu đề đối tượng. Nếu ToString()phương thức được định nghĩa trên struct được gọi trực tiếp với kiểu đóng hộp trong thiscon trỏ, thì khi cố gắng truy cập trường Atrong MyStruct, nó sẽ truy cập vào offset 0, trong kiểu đóng hộp sẽ là con trỏ tiêu đề đối tượng. Vì vậy, kiểu đóng hộp có một phương thức ẩn thực hiện việc ghi đè thực tế ToString(). Phương thức ẩn này unboxmở hộp (chỉ tính toán địa chỉ, như lệnh IL) kiểu hộp sau đó gọi tĩnh một cách tĩnh được ToString()xác định trên cấu trúc.

Tương tự như vậy, kiểu đóng hộp có một phương thức ẩn cho mỗi phương thức giao diện được triển khai thực hiện cùng một phương thức mở hộp sau đó gọi phương thức tĩnh trong cấu trúc.

 

Đặc điểm kỹ thuật CLI

quyền anh

I.8.2.4 Đối với mọi kiểu giá trị, CTS xác định một kiểu tham chiếu tương ứng được gọi là kiểu đóng hộp. Điều ngược lại là không đúng: Nói chung, các kiểu tham chiếu không có kiểu giá trị tương ứng. Biểu diễn của một giá trị của một kiểu đóng hộp (một giá trị được đóng hộp) là một vị trí nơi một giá trị của kiểu giá trị có thể được lưu trữ. Kiểu đóng hộp là một kiểu đối tượng và giá trị đóng hộp là một đối tượng.

Xác định các loại giá trị

I.8.9.7 Không phải tất cả các kiểu được định nghĩa bởi định nghĩa lớp đều là kiểu đối tượng (xem §I.8.2.3); đặc biệt, các kiểu giá trị không phải là kiểu đối tượng, nhưng chúng được định nghĩa bằng định nghĩa lớp. Định nghĩa lớp cho một kiểu giá trị xác định cả kiểu giá trị (không được đóng hộp) và kiểu được đóng hộp liên quan (xem §I.8.2.4). Các thành viên của định nghĩa lớp xác định đại diện của cả hai.

II.10.1.3 Các thuộc tính ngữ nghĩa kiểu chỉ định liệu một giao diện, lớp hoặc kiểu giá trị sẽ được xác định. Thuộc tính interface chỉ định một giao diện. Nếu thuộc tính này không có và định nghĩa mở rộng (trực tiếp hoặc gián tiếp) System.ValueType, và định nghĩa không dành cho System.Enum, thì một kiểu giá trị sẽ được xác định (§II.13). Nếu không, một lớp sẽ được xác định (§II.11).

Các loại giá trị không kế thừa

I.8.9.10 Trong biểu mẫu không đóng hộp, các kiểu giá trị không kế thừa từ bất kỳ kiểu nào. Các kiểu giá trị đóng hộp sẽ kế thừa trực tiếp từ System.ValueType trừ khi chúng là kiểu liệt kê, trong trường hợp đó, chúng sẽ kế thừa từ System.Enum. Các loại giá trị đóng hộp phải được niêm phong.

II.13 Các kiểu giá trị không được đóng hộp không được coi là kiểu con của một kiểu khác và không hợp lệ để sử dụng lệnh isinst (xem Phân vùng III) trên các kiểu giá trị không được đóng hộp. Tuy nhiên, lệnh isinst có thể được sử dụng cho các loại giá trị đóng hộp.

I.8.9.10 Một kiểu giá trị không kế thừa; thay vì kiểu cơ sở được chỉ định trong định nghĩa lớp xác định kiểu cơ sở của kiểu đóng hộp.

Loại giá trị không triển khai giao diện

I.8.9.7 Các kiểu giá trị không hỗ trợ hợp đồng giao diện, nhưng các kiểu đóng hộp liên quan của chúng thì có.

II.13 Các kiểu giá trị sẽ triển khai không hoặc nhiều giao diện, nhưng điều này chỉ có ý nghĩa ở dạng đóng hộp của chúng (§II.13.3).

I.8.2.4 Giao diện và kế thừa chỉ được định nghĩa trên các kiểu tham chiếu. Do đó, trong khi định nghĩa kiểu giá trị (§I.8.9.7) có thể chỉ định cả hai giao diện sẽ được thực hiện bởi kiểu giá trị và lớp (System.ValueType hoặc System.Enum) mà nó kế thừa, những giao diện này chỉ áp dụng cho các giá trị được đóng hộp .

Từ khóa được đóng hộp không tồn tại

II.13.1 Dạng chưa đóng hộp của một kiểu giá trị sẽ được tham chiếu bằng cách sử dụng từ khóa valuetype theo sau là tham chiếu kiểu. Dạng đóng hộp của một kiểu giá trị sẽ được tham chiếu bằng cách sử dụng từ khóa đóng hộp theo sau là tham chiếu kiểu.

Lưu ý: Ở đây sai thông số kỹ thuật, không có boxedtừ khóa.

Phần kết

Tôi nghĩ rằng một phần của sự nhầm lẫn về cách các kiểu giá trị dường như kế thừa, bắt nguồn từ thực tế là C # sử dụng cú pháp ép kiểu để thực hiện quyền anh và mở hộp, khiến có vẻ như bạn đang thực hiện các lần ép kiểu, điều này không thực sự đúng như vậy (mặc dù, CLR sẽ ném ra một InvalidCastException nếu cố gắng mở hộp sai loại). (object)myStructtrong C # tạo một thể hiện mới của kiểu đóng hộp của kiểu giá trị; nó không thực hiện bất kỳ phôi nào. Tương tự như vậy, (MyStruct)objtrong C # unboxes một kiểu đóng hộp, sao chép phần giá trị ra ngoài; nó không thực hiện bất kỳ phôi nào.


1
Cuối cùng, một câu trả lời mô tả rõ ràng cách nó hoạt động! Đây xứng đáng là câu trả lời được chấp nhận. Làm tốt lắm!
Just Shadow
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.