เหตุใด C # จึงไม่สนับสนุนการส่งคืนข้อมูลอ้างอิง


141

ฉันได้อ่านว่า. NET รองรับการส่งคืนข้อมูลอ้างอิง แต่ C # ไม่ได้ มีเหตุผลพิเศษหรือไม่? ทำไมฉันไม่สามารถทำสิ่งที่ชอบ:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

8
อืม ... การอ้างอิงโปรด?
RPM1984

5
C # มีค่าและประเภทการอ้างอิงและทั้งสองสามารถส่งคืนสิ่งที่คุณหมายถึง
รันใหม่

ฉันกำลังพูดถึงบางอย่างเช่นreturn ref x
Tom Sarduy

2
หลังจาก 4 ปีคุณสามารถทำสิ่งนี้ได้อย่างแน่นอนด้วยC# 7:)
Arghya C

คำตอบ:


188

คำถามนี้เป็นหัวข้อของบล็อกของฉันเมื่อวันที่ 23 มิถุนายน 20112011 ขอบคุณสำหรับคำถามที่ยอดเยี่ยม!

ทีม C # กำลังพิจารณาเรื่องนี้ใน C # 7 ดูhttps://github.com/dotnet/roslyn/issues/5233สำหรับรายละเอียด

อัปเดต: ฟีเจอร์นี้ทำให้เป็น C # 7!


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

ผู้แสดงความคิดเห็น "RPM1984" ด้วยเหตุผลบางอย่างขอให้มีการอ้างถึงข้อเท็จจริงนี้ RPM1984 ฉันขอแนะนำให้คุณอ่านข้อมูลจำเพาะ CLI พาร์ติชันที่ฉันส่วน 8.2.1.1, "ตัวชี้ที่มีการจัดการและประเภทที่เกี่ยวข้อง" สำหรับข้อมูลเกี่ยวกับคุณสมบัติของ. NET

เป็นไปได้ทั้งหมดที่จะสร้างเวอร์ชัน C # ซึ่งรองรับคุณสมบัติทั้งสองนี้ จากนั้นคุณสามารถทำสิ่งต่าง ๆ เช่น

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

แล้วเรียกมันด้วย

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

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

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

นอกจากนี้การทำอย่างถูกต้องจะต้องมีการเปลี่ยนแปลง CLR ตอนนี้ CLR ใช้วิธีการส่งคืนโดยอ้างอิงเป็นถูกต้องตามกฎหมายแต่ไม่สามารถพิสูจน์ได้เพราะเราไม่มีเครื่องมือตรวจจับที่ตรวจพบสถานการณ์นี้:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 ส่งคืนเนื้อหาของตัวแปรโลคัลของ M2 แต่อายุการใช้งานของตัวแปรนั้นสิ้นสุดแล้ว! เป็นไปได้ที่จะเขียนเครื่องตรวจจับที่กำหนดการใช้ ref-return ที่ชัดเจนไม่ละเมิดความปลอดภัยของสแต็ก สิ่งที่เราจะทำคือเขียนตัวตรวจจับเช่นนั้นและหากตัวตรวจจับไม่สามารถพิสูจน์ความปลอดภัยของสแต็คได้เราจะไม่อนุญาตให้ใช้การส่งคืนค่าอ้างอิงในส่วนของโปรแกรมนั้น มันไม่ใช่งานที่ต้องทำมากนัก แต่มันเป็นภาระมากมายสำหรับทีมทดสอบเพื่อให้แน่ใจว่าเราได้รับทุกกรณีจริง ๆ เป็นเพียงอีกสิ่งหนึ่งที่เพิ่มค่าใช้จ่ายของคุณลักษณะไปยังจุดที่ตอนนี้ผลประโยชน์ไม่ได้มีค่าเกินดุล

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

(ดูคำถามที่เกี่ยวข้องเพิ่มเติมเป็นไปได้หรือไม่ที่จะส่งคืนการอ้างอิงถึงตัวแปรใน C #และฉันสามารถใช้การอ้างอิงภายในฟังก์ชัน C # เช่น C ++ ได้หรือไม่ )


4
@EricLippert: ฉันไม่มีตัวอย่างที่น่าเชื่อถือเป็นเพียงสิ่งที่ฉันสงสัย การตอบสนองที่ยอดเยี่ยมและน่าสนใจ
Tom Sarduy

1
@Eric: ในตัวอย่างของคุณจะไม่เหมาะกว่าที่จะy มีชีวิตอยู่หลังจากกลับมาจากM2ไหน? ฉันคาดหวังว่าฟีเจอร์นี้จะทำงานเหมือนกับ lambdas ที่จับภาพชาวบ้านได้ หรือพฤติกรรมที่คุณเสนอเพราะ CLR จัดการกับสถานการณ์นั้นอย่างไร
Fede

3
@Eric: IMHO ความสามารถในการมีคุณสมบัติส่งคืนการอ้างอิงถึงประเภทค่าเป็นการละเว้นที่สำคัญในภาษา. net ถ้า Arr เป็น Array ของประเภทค่า (เช่น Point) เราสามารถพูดได้เช่น Arr (3) .X = 9 และรู้ว่าไม่มีการเปลี่ยนแปลงมูลค่าของ Arr (9) .X หรือแม้แต่ SomeOtherArray (2) X; ถ้า Arr เป็นอาร์เรย์ของประเภทอ้างอิงบางประเภทจะไม่มีการค้ำประกันดังกล่าว ความจริงที่ว่าโอเปอเรเตอร์การทำดัชนีของอาร์เรย์ส่งคืนการอ้างอิงมีประโยชน์มาก ฉันคิดว่ามันน่าเสียดายอย่างมากที่ไม่มีคอลเลกชันประเภทอื่น ๆ ที่สามารถใช้งานได้
supercat

4
เอริคเป็นวิธีที่ดีที่สุดในการให้ข้อเสนอแนะเกี่ยวกับสถานการณ์ (ตามที่คุณแนะนำในย่อหน้าสุดท้าย) เพื่อเพิ่มโอกาสที่จะได้เห็น MS Connect? UserVoice (มีขีด จำกัด 10 โพสต์ / โหวตโดยพลการ)? อื่น ๆ อีก?
Roman Starkov

1
@ThunderGr: นั่นคือปรัชญา C # สำหรับ "ไม่ปลอดภัย" - ถ้าคุณเขียนรหัสที่อาจเป็นหน่วยความจำไม่ปลอดภัย C # ยืนยันว่าคุณทำเครื่องหมายว่า "ไม่ปลอดภัย" เพื่อให้คุณรับผิดชอบต่อความปลอดภัยของหน่วยความจำ C # มีฟีเจอร์ที่ไม่ปลอดภัยอยู่แล้วโดยที่ตัวแปรที่เป็นปัญหานั้นเป็นประเภทที่ไม่ได้รับการจัดการ คำถามคือทีม C # ควรสร้างรุ่นที่ไม่ปลอดภัยซึ่งจัดการกับประเภทที่มีการจัดการหรือไม่ หากการทำเช่นนั้นทำให้นักพัฒนาสามารถเขียนบั๊กที่น่ากลัวได้ง่ายมันจะไม่เกิดขึ้น C # ไม่ใช่ C ++ ภาษาที่ทำให้เขียนข้อผิดพลาดได้ง่าย C # ปลอดภัยจากการออกแบบ
Eric Lippert

21

คุณกำลังพูดถึงวิธีการที่คืนค่าการอ้างอิงถึงประเภทค่า ตัวอย่างในตัวเท่านั้นใน C # ที่ฉันรู้คือ array-accessor ของประเภทค่า:

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

และตอนนี้สร้างอาร์เรย์ของ struct นั้น:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

ในกรณีนี้ตัวสร้างดัชนีpoints[0]อาร์เรย์ส่งคืนการอ้างอิงถึง struct มันเป็นไปไม่ได้ที่จะเขียนดรรชนีของคุณเอง (ตัวอย่างเช่นสำหรับคอลเลกชันที่กำหนดเอง) ซึ่งมีพฤติกรรม "ส่งคืนการอ้างอิง" ที่เหมือนกัน

ฉันไม่ได้ออกแบบภาษา C # ดังนั้นฉันจึงไม่ทราบเหตุผลทั้งหมดที่ไม่สนับสนุน แต่ฉันคิดว่าคำตอบสั้น ๆ อาจเป็น: เราสามารถเข้ากันได้ดีหากไม่มีมัน


8
ทอมถามเกี่ยวกับวิธีการที่กลับอ้างอิงกับตัวแปร ตัวแปรไม่จำเป็นต้องเป็นประเภทค่าแม้ว่าจะเป็นสิ่งที่คนต้องการเมื่อพวกเขาต้องการวิธีการส่งคืน มิฉะนั้นการวิเคราะห์ที่ดี; คุณถูกต้องว่ามีเพียงสถานที่เดียวในภาษา C # ที่นิพจน์ที่ซับซ้อนสร้างการอ้างอิงถึงตัวแปรที่ผู้ใช้สามารถจัดการได้คือตัวสร้างดัชนีอาร์เรย์ (และแน่นอนว่าโอเปอเรเตอร์การเข้าถึงสมาชิก "." ระหว่างตัวรับและฟิลด์ แต่นั่นก็เห็นได้ชัดว่าเป็นการเข้าถึงตัวแปร)
Eric Lippert

1

คุณสามารถทำสิ่งต่าง ๆ เช่น:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}

1

C # 7.0มีการสนับสนุนสำหรับการส่งคืนข้อมูลอ้างอิง ดูคำตอบของฉันที่นี่

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