มี“ ตรงข้าม” กับตัวดำเนินการการรวมกันเป็นโมฆะหรือไม่? (…เป็นภาษาอะไร?)


93

การรวมกันเป็นโมฆะแปลได้คร่าวๆว่า return x, unless it is null, in which case return y

ฉันมักจะต้องการ return null if x is null, otherwise return x.y

ฉันสามารถใช้ return x == null ? null : x.y;

ไม่เลว แต่nullตรงกลางมักรบกวนฉัน - ดูเหมือนฟุ่มเฟือย ฉันไม่ต้องการสิ่งที่ต้องการreturn x :: x.y;ซึ่งสิ่งที่ตามมาคือการประเมินเฉพาะในกรณีที่สิ่งที่นำหน้ามันไม่ได้เป็น::null

ฉันเห็นว่านี่เกือบจะตรงข้ามกับการรวมกันเป็นโมฆะซึ่งผสมกับ terse, อินไลน์เช็คโมฆะ แต่ฉัน [ เกือบ ] แน่ใจว่าไม่มีตัวดำเนินการดังกล่าวใน C #

มีภาษาอื่นที่มีตัวดำเนินการดังกล่าวหรือไม่? ถ้าเป็นเช่นนั้นเรียกว่าอะไร?

(ฉันรู้ว่าฉันสามารถเขียนวิธีการได้ใน C # ฉันใช้return NullOrValue.of(x, () => x.y);แต่ถ้าคุณมีอะไรที่ดีกว่าฉันก็อยากเห็นเช่นกัน)


มีบางคนถามหาบางอย่างเช่น x? .y ใน C # แต่ไม่มีอะไรเหมือนที่มีอยู่
Anthony Pegram

1
@ แอนโธนี่โอจะสวย ขอบคุณ.
เจ

2
ใน c ++ นั้นง่ายพอที่จะแสดงเป็นreturn x ? x.y : NULLไฟล์. ใช่สำหรับการแปลงประเภทตัวชี้เป็นบูลีน!
Phil Miller

1
@Novelocrat นั่นเป็นหนึ่งในสิ่งที่ทำให้ฉันรำคาญมากที่สุดใน C # คือพวกเขาไม่ได้ทำตาม C หากสิ่งนั้น (อะไร) = จริงยกเว้นเมื่อเป็น (0, เท็จ, ว่าง)
คริสมาริซิช

2
@ คริส: นั่นไม่ใช่คำสั่งที่ถูกต้องเกี่ยวกับ C. หากคุณมีตัวแปรที่ไม่ใช่สเกลาร์ (เช่นโครงสร้าง) คุณจะไม่สามารถใช้สิ่งนั้นในเงื่อนไขได้
Phil Miller

คำตอบ:


62

มีตัวดำเนินการ dereferencing ที่ปลอดภัยเป็นโมฆะ (?) ใน Groovy ... ฉันคิดว่านั่นคือสิ่งที่คุณต้องการ

(เรียกอีกอย่างว่าตัวดำเนินการนำทางที่ปลอดภัย )

ตัวอย่างเช่น:

homePostcode = person?.homeAddress?.postcode

นี้จะให้ null ถ้าperson, person.homeAddressหรือperson.homeAddress.postcodeเป็นโมฆะ

(ตอนนี้มีให้บริการใน C # 6.0แต่ไม่มีในเวอร์ชันก่อนหน้านี้)


1
Groovy ยังมี "ตัวดำเนินการ Elvis" ซึ่งอนุญาตให้ใช้ค่าเริ่มต้นอื่น ๆ นอกเหนือจากnullเช่น:def bar = foo ?: "<null>"
Craig Stuntz

28
ฉันเสนอคุณสมบัตินี้สำหรับ C # 5.0 ฉันไม่รู้หรือสนใจว่า Groovy คืออะไร แต่มันใช้งานง่ายพอที่จะใช้ในภาษาใดก็ได้
György Andrasek

อย่างไรก็ตามมีการเพิ่ม C # 6 ในขณะนี้ไชโย
Jeff Mercado

1
งานตู้เซฟนำทางสำหรับตัวอย่างเล็ก ๆ น้อย ๆ ที่โพสต์ แต่คุณยังคงต้องใช้ประกอบ ternary Decimal? post = pre == null ? null : SomeMethod( pre );ถ้าคุณตั้งใจจะใช้ค่าที่ไม่ใช่โมฆะในการเรียกฟังก์ชันตัวอย่างเช่น: คงจะดีไม่น้อยถ้าฉันสามารถลดมันลงเป็น "Decimal? post = pre :: SomeMethod (pre);"
ได๋

@Dai: จากจำนวนการเรียงสับเปลี่ยนที่คุณอาจต้องการฉันมีความสุขมากพอกับสิ่งที่เรามี
Jon Skeet

36

เราถือว่าเพิ่ม?. ถึง C # 4 มันไม่ได้ตัด; เป็นคุณลักษณะที่ "น่ามี" ไม่ใช่คุณลักษณะ "ต้องมี" เราจะพิจารณาอีกครั้งสำหรับภาษาในอนาคตที่สมมุติขึ้น แต่ฉันจะไม่กลั้นหายใจรอถ้าฉันเป็นคุณ ไม่น่าจะมีความสำคัญอีกต่อไปเมื่อเวลาผ่านไป :-)


ความสะดวกในการนำฟีเจอร์ไปใช้มีผลในการตัดสินใจเพิ่มหรือไม่? ฉันถามสิ่งนี้เพราะการใช้ตัวดำเนินการนั้นดูเหมือนจะตรงไปตรงมาและง่ายต่อการเพิ่มภาษา ฉันไม่ใช่ผู้เชี่ยวชาญ (เพิ่งจบการศึกษาด้วยความรักที่ยิ่งใหญ่สำหรับ C #) ดังนั้นโปรดแก้ไขฉันด้วย!
Nick Strupat

14
@nick: แน่นอนว่าการใช้ lexer และ parser ใช้งานได้ห้านาที จากนั้นคุณเริ่มกังวลเกี่ยวกับสิ่งต่างๆเช่น "กลไก IntelliSense จัดการได้ดีหรือไม่" และ "ระบบกู้คืนข้อผิดพลาดจะจัดการกับมันอย่างไรเมื่อคุณพิมพ์ แต่ยังไม่ได้?" และมีกี่สิ่งที่แตกต่างกัน "." ค่าเฉลี่ยใน C # ต่อไปและกี่คนที่สมควรได้รับ? ก่อนหน้านี้และข้อความแสดงข้อผิดพลาดควรเป็นอย่างไรหากคุณทำผิดและคุณลักษณะนี้จะต้องมีการทดสอบเท่าใด (คำใบ้: มาก) และเราจะจัดทำเอกสารและสื่อสารการเปลี่ยนแปลงได้อย่างไรและ ...
Eric Lippert

3
@nick: เพื่อตอบคำถามของคุณใช่ค่าใช้จ่ายในการดำเนินการเป็นปัจจัยหนึ่ง แต่เป็นเพียงเล็กน้อย ไม่มีคุณสมบัติราคาถูกในระดับที่เราทำงานมีเพียงคุณสมบัติที่มีราคาแพงมากหรือน้อยเท่านั้น งาน dev มูลค่าห้าเหรียญเพื่อให้ parser ทำงานในกรณีรหัสที่ถูกต้องสามารถเปลี่ยนเป็นมูลค่าการออกแบบการใช้งานการทดสอบเอกสารและการศึกษามูลค่าหลายหมื่นดอลลาร์ได้อย่างง่ายดาย
Eric Lippert

11
@EricLippert ฉันคิดว่านี่จะเป็นหนึ่งใน MAJOR "nice to have" ซึ่งทำให้ C # ประสบความสำเร็จอย่างมากและยังสามารถตอบสนองภารกิจในการสร้างโค้ดให้กระชับและแสดงออกมากที่สุด!
Konstantin

ฉันต้องการเห็นสิ่งนี้เพิ่มสิ่งที่ฉันต้องการจริงๆคือตัวดำเนินการ elvis: stackoverflow.com/questions/2929836/…
Chris Marisic

16

หากคุณมีตรรกะบูลีนลัดวงจรชนิดพิเศษคุณสามารถทำได้ (ตัวอย่าง javascript):

return x && x.y;

ถ้าเป็นโมฆะแล้วมันจะไม่ได้ประเมินxx.y


3
นอกจากนี้ยังลัดวงจรที่ 0, "" และ NaN ดังนั้นจึงไม่ตรงข้ามกับ ?? มันตรงกันข้ามกับ ||
ritaj

7

มันรู้สึกดีที่จะเพิ่มสิ่งนี้เป็นคำตอบ

ฉันเดาว่าสาเหตุที่ไม่มีสิ่งนี้ใน C # เป็นเพราะไม่เหมือนกับตัวดำเนินการรวมตัวกัน (ซึ่งใช้ได้เฉพาะกับประเภทการอ้างอิงเท่านั้น) การดำเนินการย้อนกลับอาจให้ผลการอ้างอิงหรือประเภทค่า (เช่นclass xกับสมาชิกint y- ดังนั้นจึงน่าเสียดายที่ ใช้ไม่ได้ในหลาย ๆ สถานการณ์

ฉันไม่ได้บอกว่าฉันไม่อยากเห็นมัน!

วิธีแก้ปัญหาที่เป็นไปได้สำหรับตัวดำเนินการจะยกนิพจน์ประเภทค่าทางด้านขวามือให้เป็นค่าว่างโดยอัตโนมัติ แต่คุณมีปัญหาว่าx.yโดยที่ y เป็น int จะส่งกลับ an int?ซึ่งจะเป็นความเจ็บปวด

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


เมื่อฉันอ่านคำถามของ OP เป็นครั้งแรกปัญหาที่แน่นอนนี้ผุดขึ้นมาในหัวของฉัน แต่ฉันไม่สามารถสรุปได้ว่าจะพูดให้ชัดเจนได้อย่างไร มันเป็นปัญหาที่ร้ายแรงมาก +1
rmeador

ไม่ใช่ปัญหาร้ายแรง เพียงใช้int?เป็นค่าส่งคืนเริ่มต้นและผู้ใช้สามารถเปลี่ยนเป็น int ได้หากต้องการ ตัวอย่างเช่นหากฉันต้องการเรียกเมธอดบางอย่างLookupโดยมีค่าเริ่มต้นเป็น -1 ในกรณีที่หนึ่งในการอ้างอิงของฉันคือnull:foo?.Bar?.Lookup(baz) ?? -1
Qwertie

ถ้า?. :เป็นตัวดำเนินการด้านขวามือจะต้องเป็นประเภทเดียวกับสมาชิกที่เหมาะสมที่ให้ไว้ตรงกลาง?
supercat

6

Delphi มีตัวดำเนินการ: (แทน.) ซึ่งเป็นโมฆะ

พวกเขากำลังคิดที่จะเพิ่ม?. ตัวดำเนินการถึง C # 4.0 เพื่อทำเช่นเดียวกัน แต่มีเขียง

ในระหว่างนี้มี IfNotNull ()ซึ่งเป็นรอยขีดข่วนที่ทำให้คัน มีขนาดใหญ่กว่า?. หรือ: แต่จะช่วยให้คุณสามารถสร้างห่วงโซ่ของการดำเนินการที่จะไม่ทำให้ NullReferenceException มาที่คุณหากสมาชิกคนใดคนหนึ่งเป็นโมฆะ


ช่วยยกตัวอย่างการใช้ตัวดำเนินการนี้ได้ไหม
Max Carroll

1
?.เป็นคุณลักษณะภาษา C # 6.0 และใช่มันไม่ตรงกับสิ่งที่ OP ถามที่นี่ มาช้าดีกว่าเนอะ ^^
T_D

3

ใน Haskell คุณสามารถใช้ตัว>>ดำเนินการ:

  • Nothing >> Nothing คือ Nothing
  • Nothing >> Just 1 คือ Nothing
  • Just 2 >> Nothing คือ Nothing
  • Just 2 >> Just 1 คือ Just 1

3

Haskell มีfmapซึ่งในกรณีนี้ฉันคิดว่าเทียบเท่ากับData.Maybe.map. Haskell นั้นใช้งานได้จริงดังนั้นสิ่งที่คุณกำลังมองหาก็คือ

fmap select_y x

หากxเป็นผลตอบแทนนี้Nothing Nothingหากxเป็นผลตอบแทนนี้Just object Just (select_y object)ไม่สวยเท่าสัญกรณ์จุด แต่เนื่องจากเป็นภาษาที่ใช้งานได้สไตล์จึงแตกต่างกัน


2

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

$x = $null
$result = $x.y  # $result is null

$x = New-Object PSObject
$x | Add-Member NoteProperty y 'test'
$result = $x.y  # $result is 'test'

มันไม่สวย แต่คุณสามารถเพิ่มวิธีการขยายที่จะทำงานตามที่คุณอธิบาย

public static TResult SafeGet<T, TResult>(this T obj, Func<T, TResult> selector) {
    if (obj == null) { return default(TResult); }
    else { return selector(obj); }
}

var myClass = new MyClass();
var result = myClass.SafeGet(x=>x.SomeProp);

2
public class ok<T> {
    T s;
    public static implicit operator ok<T>(T s) { return new ok<T> { s = s }; }
    public static implicit operator T(ok<T> _) { return _.s; }

    public static bool operator true(ok<T> _) { return _.s != null; }
    public static bool operator false(ok<T> _) { return _.s == null; }
    public static ok<T> operator &(ok<T> x, ok<T> y) { return y; }
}

ฉันมักต้องการตรรกะนี้สำหรับสตริง:

using ok = ok<string>;

...

string bob = null;
string joe = "joe";

string name = (ok)bob && bob.ToUpper();   // name == null, no error thrown
string boss = (ok)joe && joe.ToUpper();   // boss == "JOE"

1

สร้างอินสแตนซ์แบบคงที่ของชั้นเรียนของคุณที่ใดที่หนึ่งโดยมีค่าเริ่มต้นที่ถูกต้องสำหรับสมาชิก

ตัวอย่างเช่น:

z = new Thingy { y=null };

แล้วแทนที่จะเป็นไฟล์

return x != null ? x.y : null;

คุณสามารถเขียน

return (x ?? z).y;

บางครั้งฉันใช้เทคนิคนั้น (เช่น (x ?? "") .oString ()) แต่ใช้ได้กับข้อมูลบางประเภทเช่น POD และสตริงเท่านั้น
Qwertie

1

สิ่งนี้จะถูกเพิ่มใน C # vNext (Roslyn ขับเคลื่อน C # เผยแพร่ด้วย Visual Studio 2014)

เรียกว่าการขยายพันธุ์ Null และแสดงไว้ที่นี่ว่าสมบูรณ์ https://roslyn.codeplex.com/wikipage?title=Language%20Feature%20Status

นอกจากนี้ยังระบุไว้ที่นี่ว่าสมบูรณ์: https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c


1

สิ่งที่เรียกว่า "ตัวดำเนินการเงื่อนไขว่าง" ถูกนำมาใช้ใน C # 6.0 และใน Visual Basic 14
ในหลาย ๆ สถานการณ์สามารถใช้เป็นสิ่งที่ตรงกันข้ามกับตัวดำเนินการที่รวมกันเป็นโมฆะ:

int? length = customers?.Length; // null if customers is null   
Customer first = customers?[0];  // null if customers is null  
int? count = customers?[0]?.Orders?.Count();  // null if customers, the first customer, or Orders is null

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators

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