ตัวดำเนินการรวมคุณสมบัติสำหรับ C #


9

ตัวดำเนินการรวมค่า null ใน c # อนุญาตให้คุณย่อรหัสได้

  if (_mywidget == null)
     return new Widget();
  else
     return _mywidget;

ลงไป:

  return _mywidget ?? new Widget();

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

  if (_mywidget == null)
     return 5;
  else
     return _mywidget.Length;

ด้วย:

  return _mywidget.Length ??! 5;

ฉันอดไม่ได้ที่จะคิดว่าต้องมีเหตุผลบางอย่างที่ผู้ดำเนินการรายนี้จะไม่มีอยู่จริง มันเป็นกลิ่นรหัสหรือไม่? มีวิธีที่ดีกว่าในการเขียนนี้หรือไม่? (ฉันรู้ถึงรูปแบบวัตถุ null แต่ดูเหมือนว่าจะใช้เกินขีดเพื่อแทนที่รหัสสี่บรรทัดนี้)


1
ผู้ประกอบการที่มีเงื่อนไขจะเพียงพอหรือไม่
อานนท์

1
มีคนเขียนสิ่งที่ช่วยให้คุณทำสิ่งนี้: string location = employee.office.address.location ?? "ไม่ทราบ"; . ชุดนี้ Wil สถานที่ที่จะ"ไม่ทราบ"เมื่อหนึ่งของวัตถุ (ทั้งพนักงาน , สำนักงาน , ที่อยู่หรือสถานที่ ) เป็นโมฆะ น่าเสียดายที่ฉันจำไม่ได้ว่าใครเขียนหรือที่เขาโพสต์ไว้ หากฉันพบมันอีกครั้งฉันจะโพสต์ไว้ที่นี่!
Kristof Claes

1
คำถามนี้จะได้รับแรงฉุดมากขึ้นใน StackOverflow
งาน

2
??!เป็นผู้ประกอบการใน C ++ :-)
James McNellis

3
สวัสดีปี 2011 เพิ่งถูกเรียกตัวเพื่อบอกว่า c # กำลังได้รับโอเปอเรเตอร์ที่ปลอดภัย
นาธานคูเปอร์

คำตอบ:


5

ฉันต้องการคุณลักษณะภาษา C # ที่ช่วยให้ฉันทำอย่างปลอดภัย x.Prop.SomeOtherProp.ThirdProp โดยไม่ต้องตรวจสอบแต่ละรายการสำหรับ null ใน codebase เดียวที่ฉันต้องทำ "traversals คุณสมบัติลึก" เหล่านั้นมากมายฉันเขียนวิธีการขยายที่เรียกว่า Navigate ที่กลืนกิน NullReferenceExceptions และกลับเป็นค่าเริ่มต้นแทน ดังนั้นฉันสามารถทำสิ่งนี้:

var propVal = myThing.Navigate(x => x.PropOne.PropTwo.PropThree.PropFour, defaultValue);

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

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


1
สิ่งที่ฉันคิด แต่ประสิทธิภาพของวิธีที่ปลอดภัยจะค่อนข้างแย่ฉันเดา (เพราะ Expression.Compile () มากมาย) ... ... น่าเสียดายที่มันไม่ได้ใช้งานใน C # (เช่น Obj.?PropA.?Prop1)
Guillaume86

x.PropOne.PropTwo.PropThree.PropFour เป็นตัวเลือกการออกแบบที่ไม่ดีเนื่องจากละเมิดกฎหมาย Demeter ออกแบบวิธีการ / คลาสของคุณใหม่เพื่อให้คุณไม่จำเป็นต้องใช้สิ่งนี้
Justin Shield

ละทิ้งการเข้าถึงคุณสมบัติในที่ลึกโดยไม่คำนึงถึงกฎของ Demeter นั้นอันตรายพอ ๆ กับการทำให้ฐานข้อมูลกลับสู่ปกติ คุณไปไกลเกินไป
เมียร์

3
ใน C # 6.0 นี้เป็นไปได้โดยใช้ Null-เงื่อนไข Operator (aka ผู้ประกอบการเอลวิส) msdn.microsoft.com/en-us/magazine/dn802602.aspx
victorvartan

15

ฉันเชื่อว่าคุณสามารถทำได้ด้วย C # 6 ในตอนนี้:

return _mywidget?.Length ?? 5;

สังเกตผู้ประกอบการที่มีเงื่อนไข?.ด้านซ้าย _mywidget?.Lengthผลตอบแทนที่เป็นโมฆะถ้า_mywidgetเป็นโมฆะ

ผู้ประกอบการที่ง่าย ๆ อาจจะอ่านง่ายกว่า แต่อย่างที่คนอื่น ๆ แนะนำ


9

เป็นเพียงการคาดเดาว่าทำไมพวกเขาไม่ทำ ท้ายที่สุดฉันไม่เชื่อ ?? อยู่ในรุ่น C # แรก

ต่อไปฉันจะใช้โอเปอเรเตอร์ที่มีเงื่อนไขและเรียกมันต่อวัน:

return (_mywidget != null) ? _mywidget.Length : 5;

?? เป็นเพียงทางลัดไปยังผู้ดำเนินการตามเงื่อนไข


5
โดยส่วนตัวแล้วสิ่งนี้เลว (imho) เหมือนกับการมีผู้ให้บริการทำสิ่งนี้ตั้งแต่แรก คุณต้องการบางสิ่งบางอย่างจากวัตถุ ... วัตถุนั้นอาจไม่อยู่ที่นั่น ... ดังนั้นให้5คำตอบในกรณีที่มันไม่มี คุณต้องดูที่การออกแบบของคุณก่อนที่คุณจะเริ่มขอตัวดำเนินการพิเศษ
Moo-Juice

1
@ Moo-Juice ฉันแค่นึกภาพคนกำลังรักษารหัสนี้ 'ทำไมถึงให้ฉัน 5 Hrmmm อะไร?!'
msarchet


3

โดยส่วนตัวฉันคิดว่ามันอ่านไม่ออก ในตัวอย่างแรกการ??แปลค่อนข้างดีว่า "คุณอยู่ที่นั่นหรือไม่ไม่ลองทำสิ่งนี้"

ในตัวอย่างที่สอง??!เห็นได้ชัดว่าเป็น WTF และไม่มีความหมายต่อโปรแกรมเมอร์ ... ไม่มีวัตถุดังนั้นฉันจึงไม่สามารถรับทรัพย์สินได้ดังนั้นฉันจะส่งคืน 5. สิ่งนี้หมายความว่าอย่างไร 5 คืออะไร เราจะสรุปได้อย่างไรว่า 5 เป็นคุณค่าที่ดี?

ในระยะสั้นตัวอย่างแรกทำให้รู้สึก ... ไม่ได้มีใหม่มัน ในวินาทีมันเปิดให้อภิปราย


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

3
สิ่งนี้ทำให้ฉันนึกถึงสิ่งที่เรียกว่า "ผู้ดำเนินการ WTF" ใน C ++ (สิ่งนี้ใช้งานได้จริง: (foo() != ERROR)??!??! cerr << "Error occurred" << endl;.)
Tamás Szelei

@whatsisname มันเป็นภาพสะท้อนมากกว่าความจริงที่ว่ามันเหมาะสมสำหรับการตัดสินใจ เพื่อสร้างวัตถุเพราะมันไม่มีเหตุผล ในการกำหนดค่า arbituary ที่ไม่มีความหมายอะไรกับผู้อ่านเพราะคุณไม่สามารถรับทรัพย์สินของบางสิ่งได้นั่นเป็นสถานการณ์ WTF
Moo-Juice

ฉันคิดว่าคุณกำลังจมดิ่งอยู่ในตัวอย่างของฉัน อาจเป็น 0 ดีกว่า 5 บางทีคุณสมบัติเป็นวัตถุดังนั้นหาก _mywidget เป็นโมฆะคุณต้องการส่งคืน foodle ใหม่ () ฉันไม่เห็นด้วยอย่างนั้น สามารถอ่านได้มากกว่า ??! แม้ว่า :)
เบ็นฟุลตัน

@ Ben, ถึงแม้ว่าผมจะยอมรับว่ามุ่งเน้นไปที่ผู้ประกอบการที่ตัวเองไม่ได้ยืมมากเกินไปที่จะตอบคำถามของคุณผม twas 5วาดภาพคู่ขนานกับการรับรู้ของโปรแกรมเมอร์และพลว่า ฉันคิดว่ามีปัญหาอีกมากที่คุณต้องทำอะไรแบบนี้ในขณะที่ในตัวอย่างแรกของคุณความต้องการค่อนข้างชัดเจนโดยเฉพาะอย่างยิ่งในสิ่งต่าง ๆ เช่นผู้จัดการแคช
Moo-Juice

2

ฉันคิดว่าผู้คนกำลังติดอยู่ใน "5" ที่คุณกลับมา ... บางที 0 น่าจะดีกว่า :)

อย่างไรก็ตามฉันคิดว่าปัญหาคือว่า??!มันไม่ได้เป็นผู้ประกอบการแบบสแตนด์อโลน พิจารณาสิ่งนี้หมายความว่า:

var s = myString ??! "";

ในกรณีนั้นมันไม่มีเหตุผล: มันสมเหตุสมผลถ้าตัวถูกดำเนินการทางด้านซ้ายเป็นตัวเข้าถึงคุณสมบัติ หรือเกี่ยวกับสิ่งนี้:

var s = Foo(myWidget.Length) ??! 0;

มีคุณสมบัติในการเข้าถึงมี แต่ฉันยังคงไม่คิดว่ามันจะทำให้ความรู้สึก (ถ้าmyWidgetเป็นnullหมายความว่าเราไม่ได้โทรFoo()เลย?) หรือนี้เป็นเพียงข้อผิดพลาด?

ฉันคิดว่าปัญหาคือมันไม่เหมาะกับภาษาตาม??ปกติ


1

มีคนสร้างคลาสยูทิลิตี้ที่จะทำเพื่อคุณ แต่ฉันหามันไม่เจอ สิ่งที่ฉันพบคือสิ่งที่คล้ายกันในฟอรัม MSDN (ดูคำตอบที่สอง)

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


1

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

ตัวอย่างเช่นคุณมีวัตถุที่ผูกไว้กับแผนที่และคุณต้องการให้วัตถุอยู่ในระดับความสูงที่เหมาะสม แผนที่ DTED สามารถมีช่องว่างในข้อมูลได้ดังนั้นจึงเป็นไปได้ที่จะมีค่า Null รวมถึงค่าในช่วงของบางอย่างเช่น -100 ถึง ~ 8900 เมตร คุณอาจต้องการบางสิ่งบางอย่างตาม:

mapObject.Altitude = mapObject.Coordinates.DtedAltitude ?? DEFAULT_ALTITUDE;

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

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


1

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

return ( _mywidget ?? new MyWidget() {length = defaultLength}).Length;

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

อีกทางเลือกหนึ่งคือการใช้รูปแบบ NullObject คุณกำหนดอินสแตนซ์แบบคงที่เดียวของคลาสด้วยค่าเริ่มต้นที่สมเหตุสมผลและใช้แทน

return ( _mywidget ?? myWidget.NullInstance).Length;

0

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

หากคุณมีชุดค่าเริ่มต้นสำหรับวัตถุคุณสามารถ subclass เพื่อสร้างอินสแตนซ์ซิงเกิลเริ่มต้น

สิ่งที่ต้องการ: return (myobj ?? default_obj) ความยาว


0

โดยปกติคุณสมบัติความยาวเป็นประเภทค่าจึงไม่สามารถเป็นค่าว่างได้ดังนั้นตัวดำเนินการรวมค่าว่างจึงไม่สมเหตุสมผล

แต่คุณสามารถทำได้ด้วยคุณสมบัติที่เป็นประเภทการอ้างอิงตัวอย่างเช่น: var window = App.Current.MainWindow ?? new Window();


ตัวอย่างกำลังพยายามพิจารณาว่าจะเกิดอะไรขึ้นกับสถานการณ์ของคุณหากกระแสเป็นโมฆะ ตัวอย่างของคุณจะผิดพลาด ... แต่การมีโอเปอเรเตอร์ที่สามารถรวมตัวกันที่ใดก็ได้ในห่วงโซ่การแสดงทิศทางผิดจะมีประโยชน์
เมียร์

-1

ฉันเคยเห็นใครบางคนที่มีแนวคิดคล้ายกันมาก่อน แต่ส่วนใหญ่คุณจะต้องการกำหนดค่าใหม่ให้กับเขตข้อมูล null เช่น:

return _obj ?? (_obj = new Class());

ดังนั้นแนวคิดก็คือการรวม??และการ=มอบหมายเข้ากับ:

return _obj ??= new Class();

ฉันคิดว่านี่เหมาะสมกว่าใช้เครื่องหมายอัศเจรีย์

ความคิดนี้ไม่ใช่ของฉัน แต่ฉันชอบมันมาก :)


1
คุณตกเป็นเหยื่อของตัวอย่างที่ไม่ดีหรือไม่? ตัวอย่างของคุณสั้นลงreturn _obj ?? new Class();ได้โดยไม่จำเป็นต้องอยู่??=ที่นี่
Matt Ellen

@ Matt Ellen คุณเข้าใจผิด ได้รับมอบหมายจะตั้งใจ ฉันแนะนำทางเลือกอื่น มันไม่เหมือนกับสิ่งที่ OP ร้องขอ แต่ฉันเชื่อว่ามันจะมีประโยชน์
chakrit

2
ทำไมคุณถึงต้องการมอบหมายถ้าคุณกำลังจะคืนค่า?
Matt Ellen

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