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?
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?
Câu trả lời:
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ừa và dẫ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.
Đ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 đó ValueType
thậ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.
ValueType
là đặc biệt, nhưng điều đáng nói là ValueType
nó thực sự là một kiểu tham chiếu.
Đâ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).
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.
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 # ...
System.ValueType
) và cấm các lớp chỉ định rằng chúng kế thừa từ đó System.ValueType
vì 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ị.
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.
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.
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.
struct
và class
là 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 interface
thuộc tính ngữ nghĩa:
.class interface MyInterface
{
}
Lý do mà các cấu trúc có thể kế thừa System.ValueType
và 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, và thực hiện System.ICloneable
.
Giải thích là, khi định nghĩa lớp mở rộng System.ValueType
nó 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 extends
và implements
từ 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:
System.ValueType
và triển khai System.ICloneable
giao diện.Cũng lưu ý rằng bất kỳ định nghĩa lớp nào mở rộng System.ValueType
về bản chất cũng được đóng dấu, cho dù sealed
từ 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ừ struct
khó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 boxed
có 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ó class
và valuetype
. Nếu chúng ta sử dụng, valuetype MyNamespace.MyType
chú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 boxed
từ 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 managed
lấ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 managed
lấ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 boxed
là một từ khóa, .method static void Print(boxed MyNamespace.MyType test) cil managed
sẽ 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
, object
hoặc boxed MyNamespace.MyValueType
như 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ị.
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.ValueType
hoặ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.ValueType
và có 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.
interface
thuộc tính ngữ nghĩa được chỉ định, định nghĩa lớp xác định một giao diện.interface
thuộ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).interface
thuộ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ị và loại đóng hộp tương ứng của nó (struct).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.
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 this
con trỏ, thì khi cố gắng truy cập trường A
trong 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 unbox
mở 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.
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.
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).
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.
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 .
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ó boxed
từ khóa.
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)myStruct
trong 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)obj
trong 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.
System.ValueType
loại trong hệ thống loại CLR.