ความแตกต่างระหว่างประเภทการอ้างอิงและประเภทค่าใน c # คืออะไร?


100

ผู้ชายบางคนถามคำถามนี้กับฉันเมื่อสองเดือนก่อนและฉันไม่สามารถอธิบายรายละเอียดได้ ความแตกต่างระหว่างประเภทอ้างอิงและประเภทค่าใน C # คืออะไร?

ฉันรู้ว่ามีค่าชนิดint, bool, floatฯลฯ และการอ้างอิงประเภทdelegate, interfaceฯลฯ หรือเป็นความผิดนี้มากเกินไป?

คุณช่วยอธิบายให้ฉันเข้าใจแบบมืออาชีพได้ไหม


3
ตามบันทึกเล็ก ๆ ฉันคิดว่าคำถามนี้ถามเกี่ยวกับ C # แต่ในความเป็นจริงมันเกี่ยวกับ C # + .NET คุณไม่สามารถวิเคราะห์ C # โดยไม่ต้องวิเคราะห์. NET ฉันจะไม่ติดแท็กคำถามซ้ำเพราะอาจมีบางประเด็นที่ต้องทำในการวิเคราะห์หนึ่งโดยไม่ต้องวิเคราะห์อีกอันหนึ่ง (ตัววนซ้ำและการปิดฉันกำลังมองหาคุณ)
xanatos

@xanatos เป็นคำถามที่เหมาะสมที่สุดเกี่ยวกับ CLI ซึ่ง C #, VB.Net และ Net ต่างก็มีเหมือนกัน ควรมีแท็กสำหรับ CLI แต่ CLI ถูกนำไปใช้อย่างอื่น มี CLR แต่นั่นคือการนำไปใช้ไม่ใช่มาตรฐาน
user34660

คำตอบ:


172

ตัวอย่างของคุณจะแปลก ๆ เพราะในขณะที่int, boolและfloatประเภทเฉพาะอินเตอร์เฟซและผู้แทนเป็นชนิดประเภท - เช่นเดียวstructและenumชนิดของประเภทค่า

ผมเคยเขียนคำอธิบายของประเภทของการอ้างอิงและประเภทค่าในบทความนี้ เรายินดีที่จะขยายความในส่วนที่คุณพบว่าสับสน

เวอร์ชัน "TL; DR" คือการคิดว่าค่าของตัวแปร / นิพจน์ของประเภทใดประเภทหนึ่งเป็นเท่าใด สำหรับชนิดค่าค่าคือข้อมูลเอง สำหรับชนิดการอ้างอิงค่าเป็นการอ้างอิงซึ่งอาจเป็นโมฆะหรืออาจเป็นวิธีการนำทางไปยังวัตถุที่มีข้อมูล

ตัวอย่างเช่นคิดว่าตัวแปรเป็นเหมือนกระดาษ มันอาจมีค่า "5" หรือ "เท็จ" เขียนอยู่ก็ได้ แต่มันไม่มีบ้านของฉัน ... มันจะต้องมีเส้นทางไปบ้านฉัน ทิศทางเหล่านั้นเทียบเท่ากับข้อมูลอ้างอิง โดยเฉพาะอย่างยิ่งคนสองคนอาจมีกระดาษคนละแผ่นที่มีทิศทางเดียวกันกับบ้านของฉันและถ้ามีคนหนึ่งทำตามคำแนะนำเหล่านั้นและทาสีบ้านของฉันเป็นสีแดงคนที่สองก็จะเห็นการเปลี่ยนแปลงนั้นเช่นกัน ถ้าทั้งคู่มีรูปบ้านของฉันแยกจากกันบนกระดาษคนหนึ่งระบายสีกระดาษของพวกเขาจะไม่เปลี่ยนกระดาษของอีกฝ่ายเลย


2
สิ่งสำคัญคือต้องสังเกตว่ามีความหมายหลักที่แตกต่างกันสามประเภทที่สามารถนำเสนอได้ ได้แก่ ความหมายที่ไม่เปลี่ยนรูปความหมายของค่าที่ไม่แน่นอนและความหมายอ้างอิงที่ไม่แน่นอน ตามแนวคิดแล้วประเภทของความหมายของสิ่งที่ใช้นั้นตั้งฉากกับว่ามันถูกเก็บไว้เป็นวัตถุฮีปแบบสแตนด์อโลนหรือตัวแปร / ฟิลด์ (โครงสร้าง) ในทางปฏิบัติในขณะที่โครงสร้างที่ไม่เปิดเผยฟิลด์ของพวกเขาสามารถใช้ความหมายแบบใดก็ได้ แต่. net อนุญาตให้มีการแบ่งปันการอ้างอิงฮีปแบบไม่ จำกัด หมายความว่าอ็อบเจ็กต์ฮีปไม่สามารถใช้ค่าความหมายที่ไม่แน่นอนได้
supercat

ฉันไม่เข้าใจเลยสักนิด - 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. คุณหมายถึงอะไรโดย int, bool เป็นประเภทเฉพาะ? ทุกอย่างใน C # เช่น int, bool, float, class, interface, delegate เป็นประเภท (ชนิดข้อมูลต้องแม่นยำ) ประเภทข้อมูลจะแยกเป็น 'ประเภทอ้างอิง' และ 'ประเภทค่า' ใน C # แล้วทำไมคุณถึงบอกว่า int เป็นประเภทเฉพาะ แต่อินเทอร์เฟซเป็นประเภทหนึ่ง?
RBT

2
@RBT: ประเภทข้อมูลไม่ได้เพียงแค่แยกเป็นประเภท "อ้างอิง" และ "ประเภทค่า" นอกจากนี้ยังแยกออกเป็น "class, struct, enum, delegate, interface" intเป็นโครงสร้างstringเป็นคลาสActionเป็นตัวแทน ฯลฯ รายการ "int, bool, float, class, interface, delegate" คือรายการที่มีชนิดของสิ่งต่าง ๆ ในลักษณะเดียวกับที่ "10, int" คือ รายการที่มีสิ่งต่างๆ
Jon Skeet

@JonSkeet อาจเป็นไปได้ว่าคำตอบในโพสต์นี้อาจทำให้เข้าใจผิดได้เล็กน้อย
RBT

@RBT: ฉันจะบอกว่ามันค่อนข้างแย่ แต่ก็ไม่แย่
Jon Skeet

27

ประเภทค่า:

เก็บค่าบางอย่างไม่ใช่ที่อยู่หน่วยความจำ

ตัวอย่าง:

โครงสร้าง

การจัดเก็บ:

TL; DR : ค่าของตัวแปรจะถูกเก็บไว้ทุกที่ที่มีการประกาศ ตัวแปรโลคัลอาศัยอยู่บนสแต็กตัวอย่างเช่น แต่เมื่อประกาศภายในคลาสเป็นสมาชิกมันจะอยู่บนฮีพอย่างแน่นหนาควบคู่กับคลาสที่ถูกประกาศ
อีกต่อไป : ดังนั้นชนิดของค่าจะถูกเก็บไว้ทุกที่ที่มีการประกาศ เช่นintค่าของภายในฟังก์ชันที่เป็นตัวแปรโลคัลจะถูกเก็บไว้ในสแต็กในขณะที่ค่า in intที่ประกาศว่าเป็นสมาชิกในคลาสจะถูกเก็บไว้ในฮีปพร้อมกับคลาสที่มีการประกาศประเภทค่าบน คลาสมีอายุการใช้งานที่เหมือนกับคลาสที่ประกาศไว้ทุกประการโดยแทบไม่ต้องใช้งานโดยคนเก็บขยะ มันซับซ้อนกว่านี้ฉันขออ้างถึงหนังสือ " C # In Depth " ของ @ JonSkeetหน่วยความจำใน. NET "เพื่อการอธิบายที่กระชับยิ่งขึ้น

ข้อดี:

ประเภทค่าไม่จำเป็นต้องมีการรวบรวมขยะเพิ่มเติม จะได้รับขยะที่รวบรวมพร้อมกับอินสแตนซ์ที่มีอยู่ตัวแปรท้องถิ่นในวิธีการจะได้รับการล้างข้อมูลเมื่อทิ้งวิธี

ข้อเสีย:

  1. เมื่อส่งผ่านค่าชุดใหญ่ไปยังเมธอดตัวแปรที่ได้รับจะคัดลอกดังนั้นจึงมีค่าซ้ำซ้อนสองค่าในหน่วยความจำ

  2. เนื่องจากชั้นเรียนพลาดไปมันสูญเสียผลประโยชน์ทั้งหมดของ oop

ประเภทอ้างอิง:

เก็บแอดเดรสหน่วยความจำของค่าไม่ใช่ค่า

ตัวอย่าง:

คลาส

การจัดเก็บ:

เก็บไว้ในกอง

ข้อดี:

  1. เมื่อคุณส่งตัวแปรอ้างอิงไปยังเมธอดและมันเปลี่ยนแปลงมันจะเปลี่ยนค่าดั้งเดิมอย่างแท้จริงในขณะที่ในประเภทค่าจะมีการคัดลอกของตัวแปรที่กำหนดและค่านั้นจะเปลี่ยนไป

  2. เมื่อขนาดของตัวแปรเป็นประเภทอ้างอิงที่ใหญ่กว่าจะดี

  3. เนื่องจากคลาสมาเป็นตัวแปรประเภทอ้างอิงจึงให้ความสามารถในการนำกลับมาใช้ใหม่จึงเป็นประโยชน์ต่อการเขียนโปรแกรมเชิงวัตถุ

ข้อเสีย:

การอ้างอิงงานเพิ่มเติมเมื่อจัดสรรและ dereferences เมื่ออ่าน value.extra overload สำหรับตัวรวบรวมขยะ


5
ไม่จำเป็นต้องเป็นความจริงที่ประเภทการอ้างอิงจะถูกเก็บไว้ในฮีปและประเภทค่าจะถูกเก็บไว้ในสแตก อ่านyoda.arachsys.com/csharp/memory.htmlหากคุณต้องการเรียนรู้เพิ่มเติม
Rhys

1
คำตอบนี้มีความเข้าใจผิดเป็นจำนวนมาก โปรดอ่าน Jeff Richters CLR ผ่าน C # ประเภทค่าจะถูกเก็บไว้ใน Thread Stack และไม่อยู่ภายใต้การรวบรวมขยะ (GC) - ไม่มีส่วนเกี่ยวข้องกับ GC ประเภทการอ้างอิงจะถูกเก็บไว้ในฮีปที่มีการจัดการดังนั้นจึงอยู่ภายใต้ GC หาก Ref Type มีการอ้างอิงรูทจะไม่สามารถรวบรวมและได้รับการเลื่อนขั้นเป็นรุ่น ๆ ไป 0, 1 & 2 หากไม่มีการอ้างอิงรูทก็สามารถเป็น Garbage Collected & จากนั้นจะเข้าสู่กระบวนการนี้ที่เรียกว่า Resurrection โดยที่มัน ถูกฆ่าและนำกลับมามีชีวิตจากนั้นก็เก็บรวบรวมในที่สุด
Jeremy Thompson

13

ฉันพบว่ามันง่ายกว่าที่จะเข้าใจความแตกต่างของทั้งสองอย่างถ้าคุณรู้ว่าคอมพิวเตอร์จัดสรรสิ่งของในหน่วยความจำอย่างไรและรู้ว่าตัวชี้คืออะไร

การอ้างอิงมักจะเชื่อมโยงกับตัวชี้ หมายถึงที่อยู่หน่วยความจำที่ตัวแปรของคุณอาศัยอยู่จริงถือที่อยู่หน่วยความจำอื่นของวัตถุจริงในตำแหน่งหน่วยความจำอื่น

ตัวอย่างที่ฉันกำลังจะยกให้นั้นเรียบง่ายกว่ามากดังนั้นให้ใช้เกลือเม็ดหนึ่ง

ลองนึกภาพหน่วยความจำคอมพิวเตอร์คือกล่อง PO จำนวนมากติดต่อกัน (เริ่มต้นด้วย PO Box 0001 ถึง PO Box n) ที่สามารถเก็บของบางอย่างไว้ข้างในได้ หากตู้ป ณ . ไม่ทำเพื่อคุณให้ลองใช้แฮชแท็กหรือพจนานุกรมหรืออาร์เรย์หรือสิ่งที่คล้ายกัน

ดังนั้นเมื่อคุณทำบางสิ่งเช่น:

var a = "สวัสดี";

คอมพิวเตอร์จะทำสิ่งต่อไปนี้:

  1. จัดสรรหน่วยความจำ (เช่นเริ่มต้นที่ตำแหน่งหน่วยความจำ 1,000 สำหรับ 5 ไบต์) และใส่ H (ที่ 1,000), e (ที่ 1001), l (ที่ 1002), l (ที่ 1003) และ o (ที่ 1004)
  2. จัดสรรที่ใดที่หนึ่งในหน่วยความจำ (พูดที่ตำแหน่ง 0500) และกำหนดให้เป็นตัวแปรก.
    มันก็เหมือนกับนามแฝง (0500 คือ a)
  3. กำหนดค่าที่ตำแหน่งหน่วยความจำนั้น (0500) ถึง 1000 (ซึ่งเป็นที่ที่สตริง Hello เริ่มต้นในหน่วยความจำ) ดังนั้นตัวแปร a จึงถือการอ้างอิงไปยังตำแหน่งหน่วยความจำเริ่มต้นที่แท้จริงของสตริง "Hello"

ประเภทค่าจะเก็บของจริงไว้ในตำแหน่งหน่วยความจำ

ดังนั้นเมื่อคุณทำบางสิ่งเช่น:

var a = 1;

คอมพิวเตอร์จะทำสิ่งต่อไปนี้:

  1. จัดสรรตำแหน่งหน่วยความจำพูดที่ 0500 และกำหนดให้กับตัวแปร a (นามแฝงเดียวกัน)
  2. ใส่ค่า 1 ไว้ (ที่ตำแหน่งหน่วยความจำ 0500)
    สังเกตว่าเราไม่ได้จัดสรรหน่วยความจำพิเศษเพื่อเก็บค่าจริง (1) ดังนั้น a จึงถือค่าจริงและนั่นคือสาเหตุที่เรียกว่าประเภทค่า


@ จอนดีแบบนั้นทำให้สิ่งที่ฉันพูดไม่ถูกต้องฮ่า ๆ แต่อย่างที่ฉันพูดไปมันง่ายเกินไปที่จะทำความเข้าใจระหว่างสองประเภทซึ่งในกรณีของฉันฉันพบว่ามีประโยชน์ อย่างน้อยนั่นคือสิ่งที่ฉันนึกภาพไว้ในใจ :)
Jimmy Chandra

@JonSkeet ลิงก์ตายแล้วคุณมีอันที่ใช้งานได้หรือไม่?
FLonLon

1
@FLonLon: ใช่นี่คือลิงค์ใหม่: docs.microsoft.com/en-us/archive/blogs/ericlippert/…
Jon Skeet

8

นี่มาจากโพสต์ของฉันจากฟอรัมอื่นเมื่อประมาณสองปีที่แล้ว แม้ว่าภาษาจะเป็น vb.net (ตรงข้ามกับ C #) แนวคิดประเภทค่าเทียบกับประเภทการอ้างอิงจะเหมือนกันตลอดทั้ง. net และตัวอย่างยังคงมีอยู่

สิ่งสำคัญคือต้องจำไว้ว่าภายใน. net ทุกประเภทได้มาจากวัตถุประเภทพื้นฐาน ชนิดของค่าได้รับการออกแบบให้ทำงานเช่นนี้ แต่ในที่สุดก็จะสืบทอดการทำงานของวัตถุประเภทพื้นฐาน

A. ประเภทของค่าเป็นเพียงแค่นั้น - แสดงถึงพื้นที่ที่แตกต่างกันในหน่วยความจำที่เก็บค่าที่ไม่ต่อเนื่อง ชนิดของค่าเป็นขนาดหน่วยความจำคงที่และถูกเก็บไว้ในสแตกซึ่งเป็นชุดของที่อยู่ที่มีขนาดคงที่

เมื่อคุณแถลงเช่นนี้:

Dim A as Integer
DIm B as Integer

A = 3
B = A 

คุณได้ดำเนินการดังต่อไปนี้:

  1. สร้างช่องว่าง 2 ช่องในหน่วยความจำเพียงพอที่จะเก็บค่าจำนวนเต็ม 32 บิต
  2. วางค่าเป็น 3 ในการจัดสรรหน่วยความจำที่กำหนดให้ A
  3. วางค่า 3 ในการจัดสรรหน่วยความจำที่กำหนดให้กับ B โดยกำหนดให้เป็นค่าเดียวกับค่าที่เก็บไว้ใน A

ค่าของตัวแปรแต่ละตัวมีอยู่อย่างไม่น่าเชื่อในแต่ละตำแหน่งของหน่วยความจำ

ข. ประเภทอ้างอิงมีหลายขนาด ดังนั้นจึงไม่สามารถเก็บไว้ใน "Stack" ได้ (โปรดจำไว้ว่าสแต็กคือชุดของการจัดสรรหน่วยความจำขนาดคงที่) โดยจะถูกเก็บไว้ใน "Managed Heap" ตัวชี้ (หรือ "การอ้างอิง") ​​ไปยังแต่ละรายการบนฮีปที่มีการจัดการจะถูกเก็บรักษาไว้ในสแตก (เช่นเดียวกับที่อยู่) รหัสของคุณใช้พอยน์เตอร์เหล่านี้ในสแตกเพื่อเข้าถึงอ็อบเจ็กต์ที่เก็บไว้ในฮีปที่มีการจัดการ ดังนั้นเมื่อโค้ดของคุณใช้ตัวแปรอ้างอิงจึงใช้ตัวชี้ (หรือ "ที่อยู่" ไปยังตำแหน่งหน่วยความจำในฮีปที่มีการจัดการ)

สมมติว่าคุณได้สร้างคลาสชื่อ clsPerson ด้วยสตริง Property Person.Name

ในกรณีนี้เมื่อคุณสร้างคำสั่งเช่นนี้:

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

Dim p2 As Person

p2 = p1

ในกรณีข้างต้นคุณสมบัติ p1.Name จะส่งคืน "Jim Morrison" ตามที่คุณคาดหวัง คุณสมบัติ p2.Name จะส่งคืน "Jim Morrison" ตามที่คุณคาดหวังโดยบังเอิญ ฉันเชื่อว่าทั้ง p1 และ p2 แสดงที่อยู่ที่แตกต่างกันบน Stack อย่างไรก็ตามตอนนี้คุณได้กำหนดค่า p2 เป็น p1 แล้วทั้ง p1 และ p2 จะชี้ไปที่ตำแหน่งเดียวกันบนฮีปที่มีการจัดการ

ตอนนี้พิจารณาสถานการณ์นี้:

Dim p1 As clsPerson
Dim p2 As clsPerson

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

p2 = p1

p2.Name = "Janis Joplin"

ในสถานการณ์นี้คุณได้สร้างอินสแตนซ์ใหม่ของคลาสบุคคลบน Managed Heap โดยมีพอยน์เตอร์ p1 บนสแต็กซึ่งอ้างอิงอ็อบเจ็กต์ จากนั้นคุณสร้างตัวชี้ p2 อีกตัวในกองซ้อนและชี้ไปที่ที่อยู่เดียวกันบนฮีปที่มีการจัดการตามที่อ้างถึงโดย p1 (เมื่อคุณทำการกำหนด p2 = p1)

บิดมาที่นี่ เมื่อคุณกำหนดคุณสมบัติชื่อของ p2 ค่า "Janis Joplin" คุณกำลังเปลี่ยนคุณสมบัติ Name สำหรับอ็อบเจ็กต์ที่อ้างอิงโดยทั้ง p1 และ p2 ดังนั้นหากคุณรันโค้ดต่อไปนี้:

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). 

มันสมเหตุสมผลหรือไม่?

ล่าสุด. หากคุณทำสิ่งนี้:

DIm p1 As New clsPerson
Dim p2 As New clsPerson

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

ตอนนี้คุณมีวัตถุบุคคลสองชิ้นที่แตกต่างกัน อย่างไรก็ตามนาทีที่คุณทำสิ่งนี้อีกครั้ง:

p2 = p1

ตอนนี้คุณได้ชี้ทั้งคู่กลับไปที่ "Jim Morrison" แล้ว (ฉันไม่แน่ใจว่าเกิดอะไรขึ้นกับ Object บน Heap ที่อ้างถึงโดย p2 ... ฉันคิดว่าตอนนี้มันอยู่นอกขอบเขตไปแล้วนี่คือหนึ่งในพื้นที่เหล่านั้นที่จะมีใครสักคนตั้งความหวังให้ฉันตรงได้) -EDIT: ฉันเชื่อว่านี่คือเหตุผลที่คุณจะตั้งค่า p2 = ไม่มีอะไรหรือ p2 = clsPerson ใหม่ก่อนที่จะทำการมอบหมายใหม่

อีกครั้งถ้าคุณทำสิ่งนี้:

p2.Name = "Jimi Hendrix"

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

ขณะนี้ msgBoxes ทั้งสองจะส่งคืน "Jimi Hendrix"

สิ่งนี้อาจทำให้สับสนเล็กน้อยและฉันจะพูดครั้งสุดท้ายว่าฉันอาจมีรายละเอียดบางอย่างผิด

ขอให้โชคดีและหวังว่าคนอื่น ๆ ที่รู้ดีกว่าฉันจะมาช่วยชี้แจงเรื่องนี้บ้าง . .


ฉันไม่รู้ว่าทำไมคุณถึงไม่ได้รับคะแนนโหวตเลย คำตอบที่ดีช่วยให้ฉันเข้าใจด้วยตัวอย่างง่ายๆที่ชัดเจน
Harry

สำหรับแนวคิดประเภทค่าเทียบกับประเภทการอ้างอิงมีความเหมือนกันตลอดทั้ง. netพวกเขาถูกกำหนดไว้ในข้อกำหนด Common Language Infrastructure (CLI) มาตรฐาน Ecma 335 (รวมถึงมาตรฐาน ISO) นั่นคือมาตรฐานสำหรับส่วนมาตรฐานของ. Net Ecma standard 334 (รวมถึงมาตรฐาน ISO) เป็นภาษา C # และระบุไว้อย่างชัดเจนว่าการใช้งาน C # ต้องพึ่งพา CLI หรือสนับสนุนวิธีอื่นในการรับคุณสมบัติ CLI ขั้นต่ำที่กำหนดโดยมาตรฐาน C #นี้ VB.Net ไม่ใช่มาตรฐาน แต่เป็นกรรมสิทธิ์ของ Microsoft
user34660

5

ชนิดข้อมูลค่าและชนิดข้อมูลอ้างอิง

1) ค่า (มีข้อมูลโดยตรง) แต่ การอ้างอิง (หมายถึงข้อมูล)

2) ค่า (ทุกตัวแปรมีสำเนาของตัวเอง) แต่
ในการอ้างอิง (มากกว่าตัวแปรสามารถอ้างถึงวัตถุบางอย่าง)

3) ค่า (ตัวแปรการดำเนินการไม่สามารถส่งผลกระทบต่อตัวแปรอื่น) แต่ในการอ้างอิง (ตัวแปรสามารถส่งผลต่ออื่น ๆ )

4) ประเภทของค่าคือ (int, bool, float) แต่ ประเภทการอ้างอิงคือ (อาร์เรย์วัตถุคลาสสตริง)


3

ประเภทมูลค่า:

  • ขนาดหน่วยความจำคงที่

  • เก็บไว้ในหน่วยความจำ Stack

  • ถือมูลค่าที่แท้จริง

    เช่น int ถ่านบูล ฯลฯ ...

ประเภทอ้างอิง:

  • หน่วยความจำไม่คงที่

  • เก็บไว้ในหน่วยความจำ Heap

  • เก็บที่อยู่หน่วยความจำของค่าจริง

    เช่น สตริงอาร์เรย์คลาส ฯลฯ ...


1

"ตัวแปรที่ขึ้นอยู่กับประเภทค่ามีค่าโดยตรงการกำหนดตัวแปรประเภทค่าหนึ่งให้กับอีกตัวแปรหนึ่งจะคัดลอกค่าที่มีอยู่ซึ่งแตกต่างจากการกำหนดตัวแปรประเภทการอ้างอิงซึ่งคัดลอกการอ้างอิงไปยังวัตถุ แต่ไม่ใช่ตัวของวัตถุเอง" จากไลบรารีของ Microsoft

คุณสามารถหาคำตอบที่สมบูรณ์มากขึ้นที่นี่และที่นี่


1
ฉันไม่ชอบคำอธิบายนั้นเพราะดูเหมือนว่าการมอบหมายงานจะแตกต่างกันสำหรับประเภทการอ้างอิงและประเภทค่า มันไม่ ในทั้งสองกรณีจะทำให้ค่าของตัวแปร "เป้าหมาย" เท่ากับนิพจน์ - ค่าจะถูกคัดลอก ความแตกต่างอยู่ที่ค่านั้น - สำหรับประเภทการอ้างอิงค่าที่ได้รับการคัดลอกจะเป็นการอ้างอิง นั่นยังคงเป็นค่าของตัวแปร
Jon Skeet

ผมเห็นด้วยกับคุณและฉันได้รู้จักกันอยู่แล้วว่ามันอาจจะแตกต่างกันตามที่คุณสามารถอ่านในนี้บทความ แต่ฉันเพิ่งอ่านคู่มือของ Microsoft เกี่ยวกับเรื่องนี้และวิธีที่คุณอ่านหนังสือเป็นประจำ โปรดอย่าตำหนิฉัน! :)
Lucas S.

โอ้แน่ใจ ... มีเอกสาร MSDN มากมายที่พบข้อผิดพลาด :)
Jon Skeet

1

บางครั้งคำอธิบายอาจไม่ช่วยโดยเฉพาะสำหรับผู้เริ่มต้น คุณสามารถจินตนาการถึงประเภทค่าเป็นไฟล์ข้อมูลและประเภทการอ้างอิงเป็นทางลัดไปยังไฟล์

ดังนั้นหากคุณคัดลอกตัวแปรอ้างอิงคุณจะคัดลอกลิงก์ / ตัวชี้ไปยังข้อมูลจริงที่ใดที่หนึ่งในหน่วยความจำเท่านั้น หากคุณคัดลอกประเภทค่าคุณจะโคลนข้อมูลในหน่วยความจำจริงๆ


0

นี่อาจจะผิดในวิธีที่ลึกลับ แต่เพื่อให้ง่าย:

ประเภทค่าคือค่าที่ส่งผ่านตามปกติ "ตามค่า" (ดังนั้นการคัดลอก) ประเภทการอ้างอิงจะถูกส่งผ่าน "โดยการอ้างอิง" (ดังนั้นการให้ตัวชี้ไปที่ค่าดั้งเดิม) ไม่มีการรับประกันใด ๆ ตามมาตรฐาน. NET ECMA ว่าจะบันทึก "สิ่ง" เหล่านี้ไว้ที่ใด คุณสามารถสร้างการใช้งาน. NET ที่ไม่มีการซ้อนทับหรือแบบที่ไม่มีการซ้อนทับ (อันที่สองจะซับซ้อนมาก แต่คุณอาจทำได้โดยใช้เส้นใยและหลายสแต็ก)

โครงสร้างเป็นประเภทค่า (int, bool ... เป็นโครงสร้างหรืออย่างน้อยก็จำลองเป็น ... ) คลาสเป็นประเภทอ้างอิง

ประเภทค่ามาจาก System.ValueType ประเภทการอ้างอิงสืบเชื้อสายมาจาก System.Object

ตอนนี้ .. ในที่สุดคุณก็มี Value Type "อ็อบเจกต์อ้างอิง" และการอ้างอิง (ใน C ++ พวกเขาจะเรียกว่าพอยน์เตอร์ไปยังอ็อบเจ็กต์ใน. NET พวกมันจะทึบแสงเราไม่รู้ว่ามันคืออะไรจากมุมมองของเราพวกเขา เป็น "ที่จับ" กับวัตถุ) ระยะเวลาเหล่านี้คล้ายกับ Value types (ส่งต่อด้วยสำเนา) ดังนั้นวัตถุจึงประกอบด้วยวัตถุ (ชนิดการอ้างอิง) และการอ้างอิงเป็นศูนย์หรือมากกว่านั้น (ซึ่งคล้ายกับชนิดค่า) เมื่อไม่มีการอ้างอิงเป็นศูนย์ GC อาจจะรวบรวมข้อมูลนั้น

โดยทั่วไป (ในการใช้งาน "เริ่มต้น" ของ. NET) ประเภทค่าสามารถอยู่บนสแต็ก (ถ้าเป็นฟิลด์ในเครื่อง) หรือบนฮีป (หากเป็นฟิลด์ของคลาสหากเป็นตัวแปรในฟังก์ชันตัววนซ้ำ หากเป็นตัวแปรที่อ้างถึงโดยการปิดถ้าเป็นตัวแปรในฟังก์ชัน async (โดยใช้ Async CTP ที่ใหม่กว่า) ... ) ค่าอ้างอิงสามารถไปที่ฮีปเท่านั้น การอ้างอิงใช้กฎเดียวกันกับประเภทค่า

ในกรณีของ Value Type ที่ไปบน heap เนื่องจากอยู่ในฟังก์ชัน iterator ฟังก์ชัน async หรือถูกอ้างอิงโดยการปิดหากคุณดูไฟล์ที่คอมไพล์คุณจะเห็นว่าคอมไพเลอร์สร้างคลาสเพื่อใส่ตัวแปรเหล่านี้ และคลาสจะถูกสร้างขึ้นเมื่อคุณเรียกใช้ฟังก์ชัน

ตอนนี้ฉันไม่รู้ว่าจะเขียนเรื่องยาว ๆ อย่างไรและฉันมีสิ่งที่ดีกว่าที่จะทำในชีวิตของฉัน หากคุณต้องการเวอร์ชันที่ "ถูกต้อง" "เชิงวิชาการ" "ถูกต้อง" โปรดอ่านสิ่งนี้:

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

ฉันกำลังตามหามัน 15 นาที! ดีกว่าเวอร์ชัน msdn เนื่องจากเป็นบทความ "พร้อมใช้งาน" แบบย่อ


1
มันผิดมากกว่าวิธีลึกลับ มันเป็นพื้นฐานที่ไม่ถูกต้องผมว่า - เพราะค่าชนิดการอ้างอิงยังคงผ่านค่าเช่นกัน; เพียงแค่ว่าค่าเป็นข้อมูลอ้างอิงไม่ใช่วัตถุ ดูpobox.com/~skeet/csharp/parameters.html อ้อและตัวแปรโลคัลก็สามารถลงเอยด้วยฮีปได้เช่นกันเช่นหากถูกจับหรือเป็นส่วนหนึ่งของบล็อกตัววนซ้ำ
Jon Skeet

บล็อกตัวทำซ้ำจะถูกแปลงเป็นคลาสดังนั้น "ข้างหลังคุณ" จึงเป็น "ช่องของชั้นเรียน" เหมือนกันสำหรับการปิด ใช่ ... ฉันลืมเขียนความแตกต่างระหว่าง "ตัวชี้" (ข้อมูลอ้างอิง) และ "ชี้"
xanatos

@xanatos: แน่นอนว่าพวกเขาเป็นฟิลด์ของคลาสหลังจากการคอมไพล์ - แต่มันยังคงเป็นตัวแปรโลคัลในซอร์สโค้ด ฉันจะไม่เรียกการอ้างอิงตัวเองว่า "ประเภทมูลค่า" - ฉันคิดว่าฉันรู้ว่าคุณมาจากไหน แต่ฉันไม่คิดว่าจะทำให้น้ำขุ่นมัวด้วยวิธีนี้
Jon Skeet

@ จอนอือ ... เป็นแบบที่สามเนื่องจากพอยน์เตอร์เป็น "ทึบ" ใน. net และไม่ได้มาจาก ValueType แต่มีความคล้ายคลึงกับประเภทค่ามากกว่าการอ้างอิง คุณสามารถ "อ้างอิง" และ "ออก" ได้ ฉันต้องโคลนในน่านน้ำเพราะ "ใครบางคน" ต้องกำจัดการทำงานของตัววนซ้ำ
xanatos

เมื่อดูบทความที่ฉันชี้ไปตอนนี้ฉันพบว่า: "มีค่าสามประเภท: (1) อินสแตนซ์ประเภทค่า (2) อินสแตนซ์ประเภทการอ้างอิงและ (3) การอ้างอิง (รหัสใน C # ไม่สามารถจัดการ อินสแตนซ์ของประเภทการอ้างอิงโดยตรงโดยจะดำเนินการผ่านการอ้างอิงเสมอในรหัสที่ไม่ปลอดภัยประเภทตัวชี้จะถือว่าเป็นประเภทค่าเพื่อวัตถุประสงค์ในการกำหนดข้อกำหนดในการจัดเก็บของค่า ) "
xanatos

0

วิธีที่ง่ายที่สุดในการคิดประเภทการอ้างอิงคือพิจารณาว่าเป็น "object-IDs"; สิ่งเดียวที่สามารถทำได้กับ ID อ็อบเจ็กต์คือสร้างหนึ่งคัดลอกหนึ่งสอบถามหรือจัดการประเภทของหนึ่งหรือเปรียบเทียบสองเพื่อความเท่าเทียมกัน ความพยายามที่จะทำสิ่งอื่นใดด้วย object-ID จะถือเป็นชวเลขสำหรับการดำเนินการที่ระบุกับอ็อบเจ็กต์ที่อ้างถึงโดย id นั้น

สมมติว่าฉันมีสองตัวแปร X และ Y ประเภทรถ - ประเภทอ้างอิง Y ถือ "object ID # 19531" ถ้าฉันพูดว่า "X = Y" นั่นจะทำให้ X ถือ "object ID # 19531" โปรดทราบว่า X และ Y ไม่ได้ถือรถ รถหรือที่เรียกว่า "object ID # 19531" ถูกเก็บไว้ที่อื่น เมื่อฉันคัดลอก Y เป็น X สิ่งที่ฉันทำคือคัดลอกหมายเลข ID ตอนนี้สมมติว่าฉันพูดว่า X.Color = Colors.Blue คำสั่งดังกล่าวจะถือเป็นคำสั่งให้ไปที่ "object ID # 19531" แล้วทาสีฟ้า โปรดทราบว่าแม้ว่าตอนนี้ X และ Y จะอ้างถึงรถสีน้ำเงินแทนที่จะเป็นสีเหลือง แต่คำสั่งนั้นไม่ได้มีผลกับ X หรือ Y เนื่องจากทั้งสองยังคงอ้างถึง "object ID # 19531" ซึ่งยังคงเป็นรถคันเดียวกัน มาโดยตลอด


0

ประเภทตัวแปรและค่าอ้างอิงนั้นใช้งานง่ายและนำไปใช้กับโมเดลโดเมนได้ดีช่วยอำนวยความสะดวกในกระบวนการพัฒนา

เพื่อลบความเชื่อผิด ๆ เกี่ยวกับจำนวน "ประเภทมูลค่า" ฉันจะแสดงความคิดเห็นเกี่ยวกับวิธีการจัดการสิ่งนี้บนแพลตฟอร์ม NET โดยเฉพาะใน C # (CSharp) เมื่อเรียก APIS และส่งพารามิเตอร์ตามค่าโดยอ้างอิงในวิธีการและฟังก์ชันของเราและวิธีการรักษาทางเดินของค่าเหล่านี้อย่างถูกต้อง

อ่านบทความนี้ Variable Type Value and Reference in C #


นี่เป็นไซต์ถาม & ตอบเฉพาะภาษาอังกฤษขออภัย = \. อย่างไรก็ตามขอขอบคุณที่พยายามตอบ โปรดสร้างคำตอบแบบเต็มโดยมีลิงก์เป็นเครื่องมือช่วยเท่านั้น (แต่ไม่ใช่คำตอบที่ยั่งยืนแบบเต็ม) โปรดดูที่วิธีการที่จะตอบ
Jesse

0

สมมติว่าvเป็นนิพจน์ / ตัวแปรชนิดค่าและrเป็นนิพจน์ / ตัวแปรชนิดอ้างอิง

    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 .

ดังนั้นตัวแปรประเภทค่าจะเก็บค่าจริง (5 หรือ "h") ชนิดอ้างอิงจะเก็บเฉพาะลิงก์ไปยังกล่องเชิงเปรียบเทียบที่มีค่าอยู่


0

ก่อนที่จะอธิบายประเภทข้อมูลต่างๆที่มีอยู่ใน C # สิ่งสำคัญคือต้องระบุว่า C # เป็นภาษาที่พิมพ์ผิด ซึ่งหมายความว่าแต่ละตัวแปรค่าคงที่พารามิเตอร์อินพุตประเภทการส่งคืนและโดยทั่วไปทุกนิพจน์ที่ประเมินเป็นค่าจะมีชนิด

แต่ละประเภทมีข้อมูลที่คอมไพลเลอร์จะฝังลงในไฟล์ปฏิบัติการเป็นข้อมูลเมตาซึ่งจะใช้โดยรันไทม์ภาษาทั่วไป (CLR) เพื่อรับประกันความปลอดภัยของประเภทเมื่อจัดสรรและเรียกคืนหน่วยความจำ

หากคุณต้องการทราบว่าหน่วยความจำประเภทใดประเภทหนึ่งจัดสรรได้เท่าใดคุณสามารถใช้ตัวดำเนินการ sizeof ดังต่อไปนี้:

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

ผลลัพธ์จะแสดงจำนวนไบต์ที่จัดสรรโดยแต่ละตัวแปร

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

ข้อมูลที่เกี่ยวข้องกับแต่ละประเภท ได้แก่ :

  • พื้นที่จัดเก็บที่ต้องการ
  • ค่าสูงสุดและต่ำสุด ตัวอย่างเช่นประเภท Int32 ยอมรับค่าระหว่าง 2147483648 ถึง 2147483647
  • ประเภทฐานที่สืบทอดมา
  • ตำแหน่งที่จะจัดสรรหน่วยความจำสำหรับตัวแปรในขณะทำงาน
  • ประเภทของการดำเนินการที่ได้รับอนุญาต
  • สมาชิก (วิธีการฟิลด์เหตุการณ์ ฯลฯ ) ที่มีอยู่ตามประเภท ตัวอย่างเช่นหากเราตรวจสอบคำจำกัดความของประเภท int เราจะพบโครงสร้างและสมาชิกต่อไปนี้:

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

การจัดการหน่วยความจำ เมื่อหลายกระบวนการทำงานบนระบบปฏิบัติการและจำนวน RAM ไม่เพียงพอที่จะเก็บไว้ทั้งหมดระบบปฏิบัติการจะแมปส่วนต่างๆของฮาร์ดดิสก์กับ RAM และเริ่มจัดเก็บข้อมูลในฮาร์ดดิสก์ ระบบปฏิบัติการจะใช้มากกว่าตารางเฉพาะที่ที่อยู่เสมือนถูกจับคู่กับที่อยู่ทางกายภาพของผู้ติดต่อเพื่อดำเนินการตามคำขอ ความสามารถในการจัดการหน่วยความจำนี้เรียกว่าหน่วยความจำเสมือน

ในแต่ละกระบวนการหน่วยความจำเสมือนที่พร้อมใช้งานจะถูกจัดระเบียบใน 6 ส่วนต่อไปนี้ แต่สำหรับความเกี่ยวข้องของหัวข้อนี้เราจะเน้นเฉพาะสแตกและฮีป

Stack สแตกเป็นโครงสร้างข้อมูล LIFO (เข้าก่อนออกก่อน) โดยมีขนาดขึ้นอยู่กับระบบปฏิบัติการ (โดยค่าเริ่มต้นสำหรับเครื่อง ARM, x86 และ x64 สำรองของ Windows 1MB ในขณะที่สำรอง Linux จาก 2MB ถึง 8MB ขึ้นอยู่กับ รุ่น).

ส่วนนี้ของหน่วยความจำได้รับการจัดการโดยอัตโนมัติโดย CPU ทุกครั้งที่ฟังก์ชันประกาศตัวแปรใหม่คอมไพลเลอร์จะจัดสรรบล็อกหน่วยความจำใหม่ให้ใหญ่เท่ากับขนาดบนสแต็กและเมื่อฟังก์ชันสิ้นสุดลงบล็อกหน่วยความจำสำหรับตัวแปรจะถูกยกเลิกการจัดสรร

ฮีป พื้นที่หน่วยความจำนี้ไม่ได้ถูกจัดการโดยอัตโนมัติโดย CPU และขนาดของมันจะใหญ่กว่าสแต็ก เมื่อมีการเรียกคีย์เวิร์ดใหม่คอมไพลเลอร์จะเริ่มค้นหาบล็อกหน่วยความจำว่างแรกที่เหมาะกับขนาดของคำขอ และเมื่อพบมันจะถูกทำเครื่องหมายว่าสงวนไว้โดยใช้ฟังก์ชัน C ในตัว malloc () และส่งกลับตัวชี้ไปยังตำแหน่งนั้น นอกจากนี้ยังสามารถยกเลิกการจัดสรรบล็อกหน่วยความจำโดยใช้ฟังก์ชัน C ในตัวฟรี () กลไกนี้ทำให้หน่วยความจำแตกตัวและต้องใช้พอยน์เตอร์เพื่อเข้าถึงบล็อกหน่วยความจำด้านขวาซึ่งช้ากว่าสแต็กในการดำเนินการอ่าน / เขียน

ชนิดที่กำหนดเองและในตัวใน ขณะที่ C # จัดเตรียมชุดมาตรฐานของประเภทในตัวที่แสดงถึงจำนวนเต็มบูลีนอักขระข้อความและอื่น ๆ คุณสามารถใช้โครงสร้างเช่นโครงสร้างคลาสอินเทอร์เฟซและ enum เพื่อสร้างประเภทของคุณเอง

ตัวอย่างของประเภทที่กำหนดเองโดยใช้โครงสร้างของโครงสร้างคือ:

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

ประเภทค่าและข้อมูลอ้างอิง เราสามารถแบ่งประเภท C # ออกเป็นหมวดหมู่ต่อไปนี้:

  • ประเภทมูลค่า
  • ประเภทการอ้างอิง

ชนิดค่าประเภท ค่าที่ได้มาจากคลาส System.ValueType และตัวแปรประเภทนี้มีค่าภายในการจัดสรรหน่วยความจำในสแตก ประเภทของค่าสองประเภทคือโครงสร้างและ enum

ตัวอย่างต่อไปนี้แสดงสมาชิกของชนิดบูลีน อย่างที่คุณเห็นไม่มีการอ้างถึงคลาส System.ValueType อย่างชัดเจนสิ่งนี้เกิดขึ้นเนื่องจากคลาสนี้สืบทอดโดยโครงสร้าง

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

ประเภทการอ้างอิง ในทางกลับกันประเภทการอ้างอิงไม่มีข้อมูลจริงที่เก็บไว้ในตัวแปร แต่เป็นที่อยู่หน่วยความจำของฮีปที่เก็บค่า ประเภทของประเภทการอ้างอิง ได้แก่ คลาสตัวแทนอาร์เรย์และอินเทอร์เฟซ

ในขณะรันเมื่อมีการประกาศตัวแปรชนิดการอ้างอิงตัวแปรจะมีค่าว่างจนกว่าอ็อบเจ็กต์ที่สร้างขึ้นโดยใช้คีย์เวิร์ดใหม่จะถูกกำหนดให้

ตัวอย่างต่อไปนี้แสดงสมาชิกของประเภทรายการทั่วไป

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

ในกรณีที่คุณต้องการค้นหาที่อยู่หน่วยความจำของอ็อบเจ็กต์เฉพาะคลาส System.Runtime.InteropServices มีวิธีการเข้าถึงอ็อบเจ็กต์ที่มีการจัดการจากหน่วยความจำที่ไม่มีการจัดการ ในตัวอย่างต่อไปนี้เราจะใช้เมธอดแบบคงที่ GCHandle Alloc () เพื่อจัดสรรแฮนเดิลให้กับสตริงจากนั้นเมธอด AddrOfPinnedObject เพื่อดึงแอดเดรส

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

ผลลัพธ์จะเป็น

Memory address:39723832

อ้างอิงเอกสาร อย่างเป็นทางการ: https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2019


-1

มีรายละเอียดเล็ก ๆ น้อย ๆ มากมายเกี่ยวกับความแตกต่างระหว่างประเภทค่าและประเภทอ้างอิงที่ระบุไว้อย่างชัดเจนตามมาตรฐานและบางส่วนก็เข้าใจได้ไม่ยากโดยเฉพาะอย่างยิ่งสำหรับผู้เริ่มต้น

ดูECMAมาตรฐาน 33, โครงสร้างพื้นฐานภาษาทั่วไป (CLI) CLI ยังเป็นมาตรฐานโดย ISO ฉันจะให้ข้อมูลอ้างอิง แต่สำหรับ ECMA เราต้องดาวน์โหลด PDF และลิงก์นั้นขึ้นอยู่กับหมายเลขเวอร์ชัน มาตรฐาน ISO ต้องเสียเงิน

ความแตกต่างอย่างหนึ่งคือประเภทค่าสามารถใส่กล่องได้ แต่โดยทั่วไปประเภทการอ้างอิงไม่สามารถ มีข้อยกเว้น แต่ค่อนข้างมีเทคนิค

ประเภทค่าไม่สามารถมีตัวสร้างอินสแตนซ์ที่ไม่มีพารามิเตอร์หรือตัวสุดท้ายและไม่สามารถอ้างถึงตัวเองได้ การอ้างถึงตัวเองหมายถึงตัวอย่างเช่นถ้ามีค่าชนิดNodeสมาชิกของNodeจะไม่สามารถเป็นNodeได้ ฉันคิดว่ามีข้อกำหนด / ข้อ จำกัด อื่น ๆ ในข้อกำหนด แต่ถ้าเป็นเช่นนั้นก็ไม่ได้รวมเข้าด้วยกันในที่เดียว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.