Sự khác biệt giữa kiểu tham chiếu và kiểu giá trị trong c # là gì?


100

Một số người đã hỏi tôi câu hỏi này vài tháng trước và tôi không thể giải thích chi tiết. Sự khác biệt giữa kiểu tham chiếu và kiểu giá trị trong C # là gì?

Tôi biết rằng các loại giá trị là int, bool, float, vv và tài liệu tham khảo loại là delegate, interfacevv Hoặc là sai này, quá?

Bạn có thể giải thích nó cho tôi một cách chuyên nghiệp?


3
Một lưu ý nhỏ, tôi nghĩ câu hỏi được hỏi về C #, nhưng thực tế là về C # + .NET. Bạn không thể phân tích C # mà không phân tích .NET. Tôi sẽ không gắn lại thẻ cho câu hỏi vì có thể có một số điểm cần được thực hiện trên phân tích một mà không có phân tích khác (lặp và đóng cửa, tôi nhìn bạn)
xanatos

@xanatos đây là một câu hỏi thích hợp nhất về CLI mà C #, VB.Net và Net đều có điểm chung. Cần có một thẻ cho CLI nhưng CLI được lấy cho một cái gì đó khác. Có CLR nhưng đó là một triển khai, không phải là một tiêu chuẩn.
user34660

Câu trả lời:


172

Ví dụ của bạn là một chút kỳ lạ vì trong khi int, boolfloatnhiều loại cụ thể, giao diện và các đại biểu là loại loại - giống như structenumlà các loại các kiểu giá trị.

Tôi đã viết một lời giải thích của các loại tài liệu tham khảo và các loại giá trị trong bài viết này . Tôi rất sẵn lòng mở rộng bất kỳ điều gì bạn thấy khó hiểu.

Phiên bản "TL; DR" là để nghĩ về giá trị của một biến / biểu thức của một kiểu cụ thể là gì. Đối với một loại giá trị, giá trị là thông tin chính nó. Đối với một loại tham chiếu, giá trị là một tham chiếu có thể là rỗng hoặc có thể là một cách điều hướng đến một đối tượng chứa thông tin.

Ví dụ, hãy nghĩ về một biến giống như một tờ giấy. Nó có thể có giá trị "5" hoặc "false" được ghi trên đó, nhưng nó không thể có nhà của tôi ... nó sẽ phải có chỉ đường đến nhà tôi. Những hướng đó tương đương với một tham chiếu. Đặc biệt, hai người có thể có những mảnh giấy khác nhau có chứa các hướng giống nhau đến nhà tôi - và nếu một người đi theo các hướng đó và sơn nhà tôi màu đỏ, thì người thứ hai cũng sẽ thấy sự thay đổi đó. Nếu cả hai đều có những bức tranh riêng biệt về ngôi nhà của tôi trên giấy, thì một người tô màu giấy của họ sẽ không thay đổi giấy của người kia.


2
Điều quan trọng cần lưu ý là có ba loại ngữ nghĩa cơ bản riêng biệt mà một thứ có thể cung cấp: ngữ nghĩa bất biến, ngữ nghĩa giá trị có thể thay đổi và ngữ nghĩa tham chiếu có thể thay đổi. Về mặt khái niệm, loại ngữ nghĩa mà một thứ triển khai là trực giao với việc nó được lưu trữ dưới dạng một đối tượng heap độc lập hay một biến / trường (struct). Trên thực tế, trong khi các cấu trúc không hiển thị các trường của chúng có thể triển khai bất kỳ loại ngữ nghĩa nào, thực tế là .net cho phép chia sẻ bừa bãi các tham chiếu heap có nghĩa là các đối tượng heap không thể triển khai ngữ nghĩa giá trị có thể thay đổi.
supercat

Tôi không hiểu chút nào - while int, bool and float are specific types, interfaces and delegates are kinds of type - just like struct and enum are kinds of value types. Ý bạn là gì khi int, bool là các loại cụ thể? Mọi thứ trong C # như int, bool, float, class, interface, Delegate đều là một kiểu (kiểu dữ liệu chính xác). Các kiểu dữ liệu được tách biệt thành 'Kiểu tham chiếu' và 'Kiểu giá trị' trong C #. Vậy tại sao anh lại nói int là một loại hình cụ thể nhưng giao diện là một loại loại?
RBT

2
@RBT: Các kiểu dữ liệu không chỉ được tách biệt thành "kiểu tham chiếu" và "kiểu giá trị". Chúng cũng được tách biệt thành "class, struct, enum, Delegate, interface". intlà một cấu trúc, stringlà một lớp, Actionlà một đại biểu, v.v. Danh sách "int, bool, float, lớp, giao diện, đại biểu" của bạn là một danh sách chứa các loại khác nhau, giống như "10, int" một danh sách chứa các loại thứ khác nhau.
Jon Skeet

@JonSkeet Có thể câu trả lời trên bài đăng này hơi gây hiểu lầm.
RBT

@RBT: Tôi muốn nói rằng nó hơi bị nói xấu, nhưng không quá tệ.
Jon Skeet

26

Loại giá trị:

Giữ một số giá trị không phải địa chỉ bộ nhớ

Thí dụ:

Cấu trúc

Lưu trữ:

TL; DR : Giá trị của một biến được lưu trữ ở bất cứ nơi nào nó được tách ra. Ví dụ, các biến cục bộ sống trên ngăn xếp, nhưng khi được khai báo bên trong một lớp với tư cách là một thành viên, nó sống trên heap được kết hợp chặt chẽ với lớp mà nó được khai báo.
Dài hơn : Vì vậy, các kiểu giá trị được lưu trữ ở bất cứ nơi nào chúng được khai báo. Ví dụ: intgiá trị của một trong một hàm dưới dạng biến cục bộ sẽ được lưu trữ trên ngăn xếp, trong khi intgiá trị của trong được khai báo là thành viên trong một lớp sẽ được lưu trữ trên heap với lớp mà nó được khai báo. Một kiểu giá trị trên một lớp có kiểu sống hoàn toàn giống với lớp mà nó được khai báo, hầu như không cần đến công việc của bộ thu gom rác. Tuy nhiên, nó phức tạp hơn, tôi muốn tham khảo cuốn sách " C # In Depth " của @ JonSkeetBộ nhớ trong .NET "để giải thích ngắn gọn hơn.

Ưu điểm:

Loại giá trị không cần thêm bộ sưu tập rác. Nó được thu gom rác cùng với cá thể mà nó đang sống. Các biến cục bộ trong các phương thức sẽ được dọn dẹp khi phương thức rời khỏi.

Hạn chế:

  1. Khi một bộ giá trị lớn được chuyển đến một phương thức, biến nhận thực sự sẽ sao chép để có hai giá trị dư thừa trong bộ nhớ.

  2. Vì các lớp học bị bỏ lỡ. Nó mất tất cả các lợi ích ban đầu

Loại tham chiếu:

Giữ một địa chỉ bộ nhớ của một giá trị không phải giá trị

Thí dụ:

Lớp học

Lưu trữ:

Được lưu trữ trên heap

Ưu điểm:

  1. Khi bạn chuyển một biến tham chiếu cho một phương thức và nó thay đổi thì nó thực sự thay đổi giá trị ban đầu trong khi trong các kiểu giá trị, một bản sao của biến đã cho được lấy và giá trị đó được thay đổi.

  2. Khi kích thước của biến lớn hơn, kiểu tham chiếu là tốt

  3. Khi các lớp trở thành một biến kiểu tham chiếu, chúng cung cấp khả năng tái sử dụng, do đó có lợi cho lập trình hướng đối tượng

Hạn chế:

Tham khảo thêm công việc khi phân bổ và bỏ tham chiếu khi đọc giá trị. Quá tải văn bản cho trình thu gom rác


5
Không nhất thiết phải đúng khi các kiểu tham chiếu được lưu trữ trên heap và các kiểu giá trị được lưu trữ trên ngăn xếp. Đọc yoda.arachsys.com/csharp/memory.html nếu bạn muốn tìm hiểu thêm.
Rhys

1
Có rất nhiều sự hiểu lầm trong câu trả lời này. Vui lòng đọc Jeff Richters CLR qua C #. Các Loại Giá trị được lưu trữ trên Ngăn xếp Chủ đề và không bị thu gom rác (GC) - chúng không liên quan gì đến GC. Các Loại Tham chiếu được lưu trữ trên heap được quản lý và do đó phải tuân theo GC. Nếu một Loại Tham chiếu có tham chiếu gốc thì nó không thể được thu thập và được thăng cấp lên các thế hệ 0, 1 & 2. Nếu không có tham chiếu gốc, nó có thể được Thu thập rác và sau đó trải qua quá trình này được gọi là Phục sinh ở đó bị giết và làm cho cuộc sống trở lại và sau đó cuối cùng được thu thập.
Jeremy Thompson

13

Tôi thấy sẽ dễ hiểu hơn sự khác biệt của cả hai nếu bạn biết cách máy tính phân bổ các nội dung trong bộ nhớ và biết con trỏ là gì.

Tham chiếu thường được kết hợp với một con trỏ. Có nghĩa là địa chỉ bộ nhớ nơi biến của bạn cư trú thực sự đang giữ một địa chỉ bộ nhớ khác của đối tượng thực ở một vị trí bộ nhớ khác.

Ví dụ tôi sắp đưa ra là quá đơn giản hóa, vì vậy hãy lấy nó với một hạt muối.

Hãy tưởng tượng bộ nhớ máy tính là một loạt các hộp PO liên tiếp (bắt đầu từ w / PO Box 0001 đến PO Box n) có thể chứa một thứ gì đó bên trong nó. Nếu các hộp PO không làm được điều đó cho bạn, hãy thử một bảng băm hoặc từ điển hoặc một mảng hoặc thứ gì đó tương tự.

Vì vậy, khi bạn làm điều gì đó như:

var a = "Xin chào";

máy tính sẽ làm như sau:

  1. cấp phát bộ nhớ (giả sử bắt đầu từ vị trí bộ nhớ 1000 cho 5 byte) và đặt H (ở 1000), e (ở 1001), l (ở 1002), l (ở 1003) và o (ở 1004).
  2. cấp phát một nơi nào đó trong bộ nhớ (ví dụ tại vị trí 0500) và gán nó là biến a.
    Vì vậy, nó giống như một bí danh (0500 là a).
  3. gán giá trị tại vị trí bộ nhớ đó (0500) cho 1000 (là nơi bắt đầu chuỗi Hello trong bộ nhớ). Do đó, biến a đang giữ một tham chiếu đến vị trí bộ nhớ bắt đầu thực tế của chuỗi "Hello".

Loại giá trị sẽ giữ thứ thực tế trong vị trí bộ nhớ của nó.

Vì vậy, khi bạn làm điều gì đó như:

var a = 1;

máy tính sẽ làm như sau:

  1. cấp phát một vị trí bộ nhớ nói tại 0500 và gán nó cho biến a (cùng một bí danh)
  2. đặt giá trị 1 vào nó (tại vị trí bộ nhớ 0500).
    Lưu ý rằng chúng tôi không cấp phát thêm bộ nhớ để giữ giá trị thực (1). Vì vậy, a thực sự đang giữ giá trị thực và đó là lý do tại sao nó được gọi là kiểu giá trị.


@Jon, Chà, điều đó làm mất hiệu lực những gì tôi đang nói, LOL. Nhưng như tôi đã nói, thật là đơn giản hóa quá mức để có được một số hiểu biết giữa hai loại mà trong trường hợp của tôi, tôi thấy hữu ích. Ít nhất đó là cách tôi hình dung nó trong tâm trí của mình :).
Jimmy Chandra

8

Đây là từ một bài đăng của tôi từ một diễn đàn khác, khoảng hai năm trước. Mặc dù ngôn ngữ là vb.net (trái ngược với C #), các khái niệm Kiểu giá trị so với Kiểu tham chiếu là thống nhất trong toàn bộ .net và các ví dụ vẫn được giữ nguyên.

Cũng cần nhớ rằng trong .net, về mặt kỹ thuật, TẤT CẢ các kiểu đều bắt nguồn từ kiểu cơ sở Đối tượng. Các kiểu giá trị được thiết kế để hoạt động như vậy, nhưng cuối cùng chúng cũng kế thừa chức năng của Đối tượng kiểu cơ sở.

A. Các kiểu giá trị chỉ đơn giản là - chúng đại diện cho một vùng riêng biệt trong bộ nhớ nơi một GIÁ TRỊ riêng biệt được lưu trữ. Các kiểu giá trị có kích thước bộ nhớ cố định và được lưu trữ trong ngăn xếp, là tập hợp các địa chỉ có kích thước cố định.

Khi bạn tuyên bố như vậy:

Dim A as Integer
DIm B as Integer

A = 3
B = A 

Bạn đã làm như sau:

  1. Đã tạo 2 khoảng trắng trong bộ nhớ đủ để chứa các giá trị số nguyên 32 bit.
  2. Đã đặt giá trị 3 trong phân bổ bộ nhớ được gán cho A
  3. Đã đặt giá trị 3 trong phân bổ bộ nhớ được gán cho B bằng cách gán giá trị đó cùng giá trị với giá trị được giữ trong A.

Giá trị của mỗi biến tồn tại riêng biệt ở mỗi vị trí bộ nhớ.

B. Các loại tham chiếu có thể có nhiều kích cỡ khác nhau. Do đó, chúng không thể được lưu trữ trong "Ngăn xếp" (hãy nhớ rằng, ngăn xếp là một tập hợp các phân bổ bộ nhớ có kích thước cố định?). Chúng được lưu trữ trong "Managed Heap". Con trỏ (hoặc "tham chiếu") đến từng mục trên đống được quản lý được duy trì trong ngăn xếp (Giống như một Địa chỉ). Mã của bạn sử dụng các con trỏ này trong ngăn xếp để truy cập các đối tượng được lưu trữ trong đống được quản lý. Vì vậy, khi mã của bạn sử dụng một biến tham chiếu, nó thực sự đang sử dụng một con trỏ (hoặc "địa chỉ" đến một vị trí bộ nhớ trong heap được quản lý).

Giả sử bạn đã tạo một Lớp có tên clsPerson, với một chuỗi Thuộc tính Person.Name

Trong trường hợp này, khi bạn thực hiện một tuyên bố như sau:

Dim p1 As clsPerson
p1 = New clsPerson
p1.Name = "Jim Morrison"

Dim p2 As Person

p2 = p1

Trong trường hợp trên, Thuộc tính p1.Name sẽ Trả về "Jim Morrison", như bạn mong đợi. Thuộc tính p2.Name sẽ CŨNG trả về "Jim Morrison", như bạn thường mong đợi. Tôi tin rằng cả p1 và p2 đều đại diện cho các địa chỉ riêng biệt trên Ngăn xếp. Tuy nhiên, bây giờ bạn đã gán cho p2 giá trị của p1, cả p1 và p2 đều trỏ đến VỊ TRÍ CÙNG LOẠI trên heap được quản lý.

Bây giờ hãy xem xét tình huống này:

Dim p1 As clsPerson
Dim p2 As clsPerson

p1 = New clsPerson
p1.Name = "Jim Morrison"

p2 = p1

p2.Name = "Janis Joplin"

Trong trường hợp này, Bạn đã tạo một phiên bản mới của lớp người trên Managed Heap với một con trỏ p1 trên Stack tham chiếu đến đối tượng và gán lại Thuộc tính Tên của đối tượng một giá trị là "Jim Morrison". Tiếp theo, bạn tạo một con trỏ p2 khác trong Ngăn xếp và trỏ nó vào cùng một địa chỉ trên đống được quản lý như được tham chiếu bởi p1 (khi bạn thực hiện phép gán p2 = p1).

Đây là khúc quanh. Khi bạn gán thuộc tính Tên của p2 giá trị "Janis Joplin", bạn đang thay đổi thuộc tính Tên cho đối tượng ĐƯỢC THAM KHẢO bởi Cả p1 và p2, như vậy, nếu bạn chạy mã sau:

MsgBox(P1.Name)
'Will return "Janis Joplin"

MsgBox(p2.Name)
'will ALSO return "Janis Joplin"Because both variables (Pointers on the Stack) reference the SAME OBJECT in memory (an Address on the Managed Heap). 

Điều đó có ý nghĩa không?

Cuối cùng. Nếu bạn làm điều này:

DIm p1 As New clsPerson
Dim p2 As New clsPerson

p1.Name = "Jim Morrison"
p2.Name = "Janis Joplin"

Bây giờ bạn có hai Đối tượng Người riêng biệt. Tuy nhiên, phút bạn làm lại điều này:

p2 = p1

Bây giờ bạn đã trỏ cả hai trở lại "Jim Morrison". (Tôi không chắc chắn chính xác điều gì đã xảy ra với Đối tượng trên Heap được tham chiếu bởi p2.. Tôi nghĩ nó giờ đã vượt ra ngoài phạm vi. Đây là một trong những lĩnh vực mà hy vọng ai đó có thể giúp tôi thẳng thắn...). -EDIT: TÔI TIN đây là lý do tại sao bạn sẽ Đặt p2 = Không có gì HOẶC p2 = Người phụ trách mới trước khi thực hiện nhiệm vụ mới.

Một lần nữa, nếu bây giờ bạn làm điều này:

p2.Name = "Jimi Hendrix"

MsgBox(p1.Name)
MsgBox(p2.Name)

Cả hai msgBoxes bây giờ sẽ trả về "Jimi Hendrix"

Điều này có thể khá khó hiểu một chút, và tôi sẽ nói lần cuối, tôi có thể có một số chi tiết sai.

Chúc may mắn, và hy vọng những người khác hiểu rõ hơn tôi sẽ đến để giúp làm rõ điều này. . .


Tôi không biết tại sao bạn không nhận được bất kỳ phiếu bầu nào. Câu trả lời hay, đã giúp tôi hiểu với các ví dụ rõ ràng, đơn giản.
Harry

Đối với các khái niệm Kiểu giá trị và Kiểu tham chiếu là thống nhất trong toàn bộ .net, chúng thực sự được định nghĩa trong đặc tả Cơ sở hạ tầng ngôn ngữ chung (CLI), tiêu chuẩn Ecma 335 (cũng là một tiêu chuẩn ISO). Đó là tiêu chuẩn cho phần tiêu chuẩn của .Net. Tiêu chuẩn Ecma 334 (cũng là tiêu chuẩn ISO) là ngôn ngữ C # và nó tuyên bố rõ ràng rằng việc triển khai C # phải dựa vào CLI hoặc hỗ trợ một cách thay thế để đạt được các tính năng CLI tối thiểu theo yêu cầu của tiêu chuẩn C # này . VB.Net tuy nhiên không phải là một tiêu chuẩn, nó là độc quyền của Microsoft.
user34660

5

kiểu dữ liệu giá trịkiểu dữ liệu tham chiếu

1) giá trị (chứa dữ liệu trực tiếp) nhưng tham chiếu ( tham chiếu đến dữ liệu)

2) trong giá trị (mỗi biến đều có bản sao của riêng nó) nhưng
trong tham chiếu (nhiều hơn biến có thể tham chiếu đến một số đối tượng)

3) trong giá trị (biến hoạt động không thể ảnh hưởng đến biến khác) nhưng trong tham chiếu (biến có thể ảnh hưởng đến biến khác)

4) kiểu giá trị là (int, bool, float) nhưng kiểu tham chiếu là (mảng, đối tượng lớp, chuỗi)


2

Loại giá trị:

  • Kích thước bộ nhớ cố định.

  • Được lưu trữ trong bộ nhớ Stack.

  • Giữ giá trị thực tế.

    Ví dụ. int, char, bool, v.v.

Loại tham chiếu:

  • Không cố định bộ nhớ.

  • Được lưu trữ trong bộ nhớ Heap.

  • Giữ địa chỉ bộ nhớ có giá trị thực tế.

    Ví dụ. chuỗi, mảng, lớp, v.v.


1

"Các biến dựa trên kiểu giá trị trực tiếp chứa giá trị. Việc gán một biến kiểu giá trị cho một biến kiểu giá trị khác sẽ sao chép giá trị được chứa. Điều này khác với việc gán các biến kiểu tham chiếu, sao chép một tham chiếu đến đối tượng nhưng không sao chép chính đối tượng." từ thư viện của Microsoft.

Bạn có thể tìm thấy câu trả lời đầy đủ hơn ở đâyở đây .


1
Tôi không thích lời giải thích đó, vì có vẻ như phép gán hoạt động khác nhau đối với các loại tham chiếu và loại giá trị. Nó không. Trong cả hai trường hợp, nó làm cho giá trị của biến "target" bằng với biểu thức - giá trị được sao chép. Sự khác biệt là giá trị đó là gì - đối với các loại tham chiếu, giá trị được sao chép là một tham chiếu. Đó vẫn là giá trị của biến.
Jon Skeet

Tôi đồng ý với bạn và tôi đã biết rằng nó có thể khác, như bạn có thể đọc trong bài viết này . Tuy nhiên, tôi chỉ trình bày lại hướng dẫn của Microsoft về chủ đề này và cũng như cách bạn thường đọc trong sách. Xin đừng trách tôi! :)
Lucas S.

Oh chắc chắn ... có rất nhiều bit của tài liệu MSDN nơi có lỗi được tìm thấy :)
Jon Skeet

1

Đôi khi giải thích sẽ không giúp ích gì đặc biệt cho những người mới bắt đầu. Bạn có thể hình dung kiểu giá trị là tệp dữ liệu và kiểu tham chiếu là lối tắt đến tệp.

Vì vậy, nếu bạn sao chép một biến tham chiếu, bạn chỉ sao chép liên kết / con trỏ vào một dữ liệu thực ở đâu đó trong bộ nhớ. Nếu bạn sao chép một kiểu giá trị, bạn thực sự sao chép dữ liệu trong bộ nhớ.


0

Điều này có thể sai theo những cách bí truyền, nhưng để làm cho nó đơn giản:

Loại giá trị là các giá trị được truyền thông thường "theo giá trị" (vì vậy hãy sao chép chúng). Các kiểu tham chiếu được truyền "bằng tham chiếu" (vì vậy đưa một con trỏ đến giá trị ban đầu). Không có bất kỳ đảm bảo nào theo tiêu chuẩn .NET ECMA về nơi lưu "những thứ" này. Bạn có thể xây dựng một triển khai .NET không có ngăn xếp, hoặc là không có đống (thứ hai sẽ rất phức tạp, nhưng bạn có thể có thể, bằng cách sử dụng các sợi và nhiều ngăn xếp)

Các cấu trúc là kiểu giá trị (int, bool ... là cấu trúc, hoặc ít nhất là được mô phỏng như ...), các lớp là kiểu tham chiếu.

Loại giá trị giảm xuống từ System.ValueType. Loại tham chiếu đi xuống từ System.Object.

Bây giờ .. Cuối cùng, bạn có Kiểu giá trị, "đối tượng được tham chiếu" và các tham chiếu (trong C ++ chúng sẽ được gọi là con trỏ tới đối tượng. Trong .NET chúng không rõ ràng. Chúng tôi không biết chúng là gì. Theo quan điểm của chúng tôi, chúng là "tay cầm" đối với đối tượng). Những giá trị kéo dài này tương tự như Loại giá trị (chúng được chuyển bằng bản sao). Vì vậy, một đối tượng được cấu tạo bởi đối tượng (một kiểu tham chiếu) và không hoặc nhiều tham chiếu đến nó (tương tự như kiểu giá trị). Khi không có tham chiếu nào, GC có thể sẽ thu thập nó.

Nói chung (trong triển khai "mặc định" của .NET), Kiểu giá trị có thể nằm trên ngăn xếp (nếu chúng là các trường cục bộ) hoặc trên heap (nếu chúng là các trường của một lớp, nếu chúng là biến trong một hàm lặp, nếu chúng là các biến được tham chiếu bởi một bao đóng, nếu chúng là biến trong một hàm không đồng bộ (sử dụng CTP Async mới hơn) ...). Giá trị được tham chiếu chỉ có thể chuyển đến đống. Tài liệu tham khảo sử dụng các quy tắc tương tự như Loại giá trị.

Trong các trường hợp Kiểu giá trị xuất hiện trên heap vì chúng nằm trong một hàm lặp, một hàm không đồng bộ hoặc được tham chiếu bởi một bao đóng, nếu bạn xem tệp đã biên dịch, bạn sẽ thấy rằng trình biên dịch đã tạo một lớp để đặt các biến này và lớp được xây dựng khi bạn gọi hàm.

Bây giờ, tôi không biết làm thế nào để viết những điều dài, và tôi có những điều tốt hơn để làm trong cuộc sống của mình. Nếu bạn muốn có một phiên bản "chính xác" "học thuật" "đúng", hãy đọc NÀY:

http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx

Tôi đang tìm nó trong 15 phút! Nó tốt hơn các phiên bản msdn, bởi vì nó là một bài báo "sẵn sàng sử dụng" cô đọng.


1
Nó sai nhiều hơn những cách bí truyền. Đó là về cơ bản sai tôi muốn nói - bởi vì giá trị kiểu tham chiếu vẫn được truyền theo giá trị cũng; nó chỉ là giá trị là một tham chiếu, không phải một đối tượng. Xem pobox.com/~skeet/csharp/parameters.html . Ồ, và các biến cục bộ cũng có thể kết thúc trên heap, ví dụ: nếu chúng được bắt hoặc là một phần của khối trình lặp.
Jon Skeet

Các khối lặp lại được chuyển đổi thành các lớp, vì vậy "phía sau bạn" chúng là "các trường của một lớp". Tương tự cho các lần đóng cửa. Ừ ... Tôi quên để viết sự phân biệt giữa "con trỏ" (tài liệu tham khảo) và "nhọn"
xanatos

@xanatos: Chắc chắn, chúng là các trường của một lớp sau khi biên dịch - nhưng chúng vẫn là các biến cục bộ trong mã nguồn. Tôi cũng sẽ không gọi bản thân các tham chiếu là "loại giá trị" - tôi nghĩ rằng tôi biết bạn đến từ đâu, nhưng tôi không nghĩ nên làm bùn nước theo cách này.
Jon Skeet

@jon Vâng ... Chúng là loại thứ ba, vì các con trỏ "không rõ ràng" trong .net và chúng không bắt nguồn từ ValueType. Nhưng chúng giống với các kiểu giá trị hơn là các tham chiếu. Bạn có thể "ref" và "out" chúng. Tôi đã phải làm bùn nước bởi vì "ai đó" đã thử nghiệm hoạt động của các máy lặp.
xanatos

Nhìn vào bài viết mà tôi đã trỏ đến, tôi thấy: "Có ba loại giá trị: (1) trường hợp của kiểu giá trị, (2) trường hợp của loại tham chiếu và (3) tham chiếu. (Mã trong C # không thể thao tác các trường hợp của các loại tham chiếu trực tiếp; nó luôn làm như vậy thông qua một tham chiếu. Trong mã không an toàn, các loại con trỏ được coi như các loại giá trị nhằm mục đích xác định các yêu cầu lưu trữ giá trị của chúng. ) ".
xanatos

0

Cách đơn giản nhất để nghĩ về các kiểu tham chiếu là coi chúng là "ID đối tượng"; những điều duy nhất mà người ta có thể làm với ID đối tượng là tạo một ID, sao chép một, hỏi hoặc thao tác kiểu của một hoặc so sánh hai để có sự bằng nhau. Nỗ lực thực hiện bất cứ điều gì khác với ID đối tượng sẽ được coi là viết tắt để thực hiện hành động được chỉ định với đối tượng được tham chiếu bởi id đó.

Giả sử tôi có hai biến X và Y kiểu Xe - một kiểu tham chiếu. Y xảy ra để giữ "ID đối tượng # 19531". Nếu tôi nói "X = Y", điều đó sẽ khiến X giữ "ID đối tượng # 19531". Lưu ý rằng cả X và Y đều không giữ xe. Chiếc xe, còn được gọi là "ID đối tượng # 19531", được lưu trữ ở nơi khác. Khi tôi sao chép Y vào X, tất cả những gì tôi làm là sao chép số ID. Bây giờ, giả sử tôi nói X.Color = Colors.Blue. Một câu lệnh như vậy sẽ được coi là một chỉ dẫn để đi tìm "object ID # 19531" và sơn nó màu xanh lam. Lưu ý rằng mặc dù X và Y hiện đề cập đến một chiếc ô tô màu xanh lam thay vì một chiếc ô tô màu vàng, nhưng câu lệnh không thực sự ảnh hưởng đến X hoặc Y, bởi vì cả hai vẫn đề cập đến "ID đối tượng # 19531", vẫn là một chiếc xe giống như nó Luôn luôn là.


0

Các kiểu biến và Giá trị tham chiếu dễ áp ​​dụng và áp dụng tốt cho mô hình miền, tạo điều kiện thuận lợi cho quá trình phát triển.

Để loại bỏ bất kỳ huyền thoại nào xung quanh số lượng "loại giá trị", tôi sẽ bình luận về cách điều này được xử lý trên nền tảng. NET, cụ thể là trong C # (CSharp) khi được gọi là APIS và gửi các tham số theo giá trị, bằng cách tham chiếu, trong các phương thức và chức năng của chúng tôi cũng như cách xử lý chính xác các đoạn của các giá trị này.

Đọc bài viết này Giá trị kiểu biến và tham chiếu trong C #


Rất tiếc, đây là trang Hỏi & Đáp chỉ có tiếng Anh = \. Tuy nhiên, cảm ơn vì đã cố gắng trả lời. Vui lòng tạo câu trả lời đầy đủ, với các liên kết chỉ là trợ giúp (nhưng không phải là câu trả lời duy trì đầy đủ). Mời bạn xem qua cách trả lời .
Jesse

0

Giả sử vlà một biểu thức / biến kiểu giá trị và rlà một biểu thức / biến kiểu tham chiếu

    x = v  
    update(v)  //x will not change value. x stores the old value of v

    x = r 
    update(r)  //x now refers to the updated r. x only stored a link to r, 
               //and r can change but the link to it doesn't .

Vì vậy, một biến kiểu giá trị lưu trữ giá trị thực (5, hoặc "h"). Một biến kiểu tham chiếu chỉ lưu trữ một liên kết đến một hộp ẩn dụ có giá trị.


0

Trước khi giải thích các kiểu dữ liệu khác nhau có sẵn trong C #, điều quan trọng cần đề cập là C # là một ngôn ngữ được đánh máy mạnh. Điều này có nghĩa là mỗi biến, hằng số, tham số đầu vào, kiểu trả về và nói chung, mọi biểu thức đánh giá một giá trị đều có một kiểu.

Mỗi kiểu chứa thông tin sẽ được trình biên dịch nhúng vào tệp thực thi dưới dạng siêu dữ liệu sẽ được thời gian chạy ngôn ngữ chung (CLR) sử dụng để đảm bảo an toàn cho kiểu khi nó cấp phát và lấy lại bộ nhớ.

Nếu bạn muốn biết một loại cụ thể phân bổ bao nhiêu bộ nhớ, bạn có thể sử dụng toán tử sizeof như sau:

static void Main()
{
    var size = sizeof(int);
    Console.WriteLine($"int size:{size}");
    size = sizeof(bool);
    Console.WriteLine($"bool size:{size}");
    size = sizeof(double);
    Console.WriteLine($"double size:{size}");
    size = sizeof(char);
    Console.WriteLine($"char size:{size}");
}

Kết quả đầu ra sẽ hiển thị số byte được phân bổ bởi mỗi biến.

int size:4
bool size:1
double size:8
char size:2

Thông tin liên quan đến từng loại là:

  • Không gian lưu trữ cần thiết.
  • Giá trị lớn nhất và nhỏ nhất. Ví dụ: kiểu Int32 chấp nhận các giá trị từ 2147483648 đến 2147483647.
  • Loại cơ sở mà nó kế thừa.
  • Vị trí nơi bộ nhớ cho các biến sẽ được cấp phát tại thời điểm chạy.
  • Các loại hoạt động được phép.
  • Các thành viên (phương thức, trường, sự kiện, v.v.) được chứa bởi kiểu. Ví dụ: nếu chúng ta kiểm tra định nghĩa của kiểu int, chúng ta sẽ tìm thấy cấu trúc và các thành viên sau:

    namespace System
    {
        [ComVisible(true)]
        public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<Int32>, IEquatable<Int32>
        {      
            public const Int32 MaxValue = 2147483647;     
            public const Int32 MinValue = -2147483648;
            public static Int32 Parse(string s, NumberStyles style, IFormatProvider provider);    
            ... 
        }  
    }

Quản lý bộ nhớ Khi nhiều tiến trình đang chạy trên một hệ điều hành và dung lượng RAM không đủ để chứa tất cả, hệ điều hành sẽ ánh xạ các phần của đĩa cứng với RAM và bắt đầu lưu trữ dữ liệu trong đĩa cứng. Hệ điều hành sẽ sử dụng các bảng cụ thể nơi các địa chỉ ảo được ánh xạ tới các địa chỉ vật lý tương ứng của chúng để thực hiện yêu cầu. Khả năng quản lý bộ nhớ này được gọi là bộ nhớ ảo.

Trong mỗi quy trình, bộ nhớ ảo có sẵn được sắp xếp theo 6 phần sau đây nhưng đối với sự liên quan của chủ đề này, chúng tôi sẽ chỉ tập trung vào ngăn xếp và đống.

Ngăn xếp Ngăn xếp là cấu trúc dữ liệu LIFO (vào sau cùng, xuất trước), với kích thước phụ thuộc vào hệ điều hành (theo mặc định, đối với máy ARM, x86 và x64 Dự trữ của Windows là 1MB, trong khi Linux dự trữ từ 2MB đến 8MB tùy thuộc vào phiên bản).

Phần bộ nhớ này được quản lý tự động bởi CPU. Mỗi khi một hàm khai báo một biến mới, trình biên dịch sẽ cấp phát một khối bộ nhớ mới lớn bằng kích thước của nó trên ngăn xếp và khi hàm kết thúc, khối bộ nhớ cho biến sẽ được phân bổ.

Heap Vùng bộ nhớ này không được quản lý tự động bởi CPU và kích thước của nó lớn hơn ngăn xếp. Khi từ khóa mới được gọi, trình biên dịch bắt đầu tìm kiếm khối bộ nhớ trống đầu tiên phù hợp với kích thước của yêu cầu. và khi tìm thấy nó, nó được đánh dấu là dành riêng bằng cách sử dụng hàm malloc () tích hợp trong C và trả về con trỏ đến vị trí đó. Cũng có thể phân bổ khối bộ nhớ bằng cách sử dụng hàm C tích hợp free (). Cơ chế này gây ra sự phân mảnh bộ nhớ và phải sử dụng con trỏ để truy cập vào đúng khối bộ nhớ, nó chậm hơn ngăn xếp để thực hiện các thao tác đọc / ghi.

Các kiểu tùy chỉnh và tích hợp Trong khi C # cung cấp một tập hợp chuẩn các kiểu tích hợp đại diện cho số nguyên, boolean, ký tự văn bản, v.v., Bạn có thể sử dụng các cấu trúc như struct, class, interface và enum để tạo các kiểu của riêng mình.

Một ví dụ về kiểu tùy chỉnh sử dụng cấu trúc struct là:

struct Point
{
    public int X;
    public int Y;
};

Loại giá trị và tham chiếu Chúng ta có thể phân loại C # thành các loại sau:

  • Các loại giá trị
  • Các loại tham chiếu

Các kiểu giá trị Các kiểu giá trị bắt nguồn từ lớp System.ValueType và các biến thuộc kiểu này chứa các giá trị của chúng trong cấp phát bộ nhớ của chúng trong ngăn xếp. Hai loại giá trị là struct và enum.

Ví dụ sau đây cho thấy thành viên của kiểu boolean. Như bạn có thể thấy, không có tham chiếu rõ ràng nào đến lớp System.ValueType, điều này xảy ra vì lớp này được kế thừa bởi struct.

namespace System
{
    [ComVisible(true)]
    public struct Boolean : IComparable, IConvertible, IComparable<Boolean>, IEquatable<Boolean>
    {
        public static readonly string TrueString;
        public static readonly string FalseString;
        public static Boolean Parse(string value);
        ...
    }
}

Các kiểu tham chiếu Mặt khác, các kiểu tham chiếu không chứa dữ liệu thực được lưu trữ trong một biến, mà là địa chỉ bộ nhớ của heap nơi lưu trữ giá trị. Các danh mục của kiểu tham chiếu là lớp, đại biểu, mảng và giao diện.

Tại thời điểm chạy, khi một biến kiểu tham chiếu được khai báo, nó chứa giá trị null cho đến khi một đối tượng đã được tạo bằng các từ khóa mới được gán cho nó.

Ví dụ sau đây cho thấy các thành viên của danh sách kiểu chung.

namespace System.Collections.Generic
{
    [DebuggerDisplay("Count = {Count}")]
    [DebuggerTypeProxy(typeof(Generic.Mscorlib_CollectionDebugView<>))]
    [DefaultMember("Item")]
    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
    {
        ...
        public T this[int index] { get; set; }
        public int Count { get; }
        public int Capacity { get; set; }
        public void Add(T item);
        public void AddRange(IEnumerable<T> collection);
        ...
    }
}

Trong trường hợp bạn muốn tìm ra địa chỉ bộ nhớ của một đối tượng cụ thể, lớp System.Runtime.InteropServices cung cấp một cách để truy cập vào các đối tượng được quản lý từ bộ nhớ không được quản lý. Trong ví dụ sau, chúng ta sẽ sử dụng phương thức tĩnh GCHandle.Alloc () để cấp phát một xử lý cho một chuỗi và sau đó phương thức AddrOfPinnedObject để truy xuất địa chỉ của nó.

string s1 = "Hello World";
GCHandle gch = GCHandle.Alloc(s1, GCHandleType.Pinned);
IntPtr pObj = gch.AddrOfPinnedObject();
Console.WriteLine($"Memory address:{pObj.ToString()}");

Đầu ra sẽ là

Memory address:39723832

Tham khảo Tài liệu chính thức: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019


-1

Có rất nhiều chi tiết nhỏ về sự khác biệt giữa các loại giá trị và các loại tham chiếu được tiêu chuẩn nêu rõ ràng và một số trong số đó không dễ hiểu, đặc biệt là đối với người mới bắt đầu.

Xem tiêu chuẩn ECMA 33, Cơ sở hạ tầng ngôn ngữ chung (CLI) . CLI cũng được tiêu chuẩn hóa bởi ISO. Tôi sẽ cung cấp tài liệu tham khảo nhưng đối với ECMA, chúng tôi phải tải xuống một tệp PDF và liên kết đó phụ thuộc vào số phiên bản. Tiêu chuẩn ISO tốn kém tiền bạc.

Một sự khác biệt là các loại giá trị có thể được đóng hộp nhưng các loại tham chiếu nói chung không thể được. Có những ngoại lệ nhưng chúng khá kỹ thuật.

Các kiểu giá trị không được có hàm tạo hoặc trình hoàn thiện phiên bản ít tham số và chúng không thể tham chiếu đến chính chúng. Đề cập đến chúng có nghĩa là ví dụ rằng nếu có một Node kiểu giá trị thì một thành viên của Node không thể là Node . Tôi nghĩ rằng có những yêu cầu / hạn chế khác trong các thông số kỹ thuật nhưng nếu vậy thì chúng không được tập hợp lại với nhau ở một nơ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.