การตรวจสอบค่า null แบบลึกมีวิธีที่ดีกว่านี้หรือไม่?


130

หมายเหตุ:คำถามนี้ถูกถามก่อนที่จะนำของผู้ประกอบการใน C # 6 / Visual Studio 2015.?

เราทุกคนเคยไปที่นั่นเรามีคุณสมบัติที่ล้ำลึกเช่น cake.frosting.berries.loader ที่เราต้องตรวจสอบว่าเป็นโมฆะหรือไม่ดังนั้นจึงไม่มีข้อยกเว้น วิธีทำคือใช้คำสั่ง if ลัดวงจร

if (cake != null && cake.frosting != null && cake.frosting.berries != null) ...

นี่ไม่ได้สวยหรูสักเท่าไหร่และอาจมีวิธีที่ง่ายกว่าในการตรวจสอบห่วงโซ่ทั้งหมดและดูว่ามันเกิดขึ้นกับตัวแปร / คุณสมบัติว่างหรือไม่

เป็นไปได้ไหมโดยใช้วิธีการขยายบางอย่างหรืออาจเป็นคุณลักษณะของภาษาหรือเป็นความคิดที่ไม่ดี?


3
ฉันปรารถนาสิ่งนั้นบ่อยพอสมควร - แต่ความคิดทั้งหมดที่ฉันคิดขึ้นมานั้นแย่กว่าปัญหาที่เกิดขึ้นจริง
peterchen

ขอบคุณสำหรับคำตอบทั้งหมดและน่าสนใจที่เห็นว่าคนอื่น ๆ ก็มีความคิดเช่นเดียวกัน ฉันต้องคิดว่าฉันต้องการเห็นสิ่งนี้แก้ไขตัวเองได้อย่างไรและแม้ว่าวิธีแก้ปัญหาของ Eric จะดี แต่ฉันคิดว่าฉันจะเขียนอะไรแบบนี้ถ้า (IsNull (abc)) หรือถ้า (IsNotNull (abc)) แต่บางที นั่นคือรสนิยมของฉัน :)
Homde

เมื่อคุณสร้างอินสแตนซ์เปลือกน้ำฅาลมันมีคุณสมบัติของผลเบอร์รี่ดังนั้น ณ จุดนั้นในตัวสร้างของคุณคุณสามารถบอกฟรอสติ้งได้ไหมว่าเมื่อใดก็ตามที่มีการสร้างผลเบอร์รี่ที่ว่างเปล่า (ไม่ใช่โมฆะ) และเมื่อใดก็ตามที่มีการแก้ไขเปลือกน้ำฅาลจะตรวจสอบมูลค่า ????
Doug Chamberlain

ค่อนข้างเกี่ยวข้องกันอย่างหลวม ๆ เทคนิคบางอย่างที่นี่ฉันพบว่าดีกว่าสำหรับปัญหา "ค่า nulls" ที่ฉันพยายามแก้ไข stackoverflow.com/questions/818642/…
AaronLS

คำตอบ:


223

เราได้พิจารณาเพิ่มการดำเนินการใหม่ "?." เป็นภาษาที่มีความหมายตามที่คุณต้องการ (และได้รับการเพิ่มแล้วดูด้านล่าง) นั่นคือคุณจะพูด

cake?.frosting?.berries?.loader

และคอมไพเลอร์จะสร้างการตรวจสอบการลัดวงจรทั้งหมดให้คุณ

มันไม่ได้สร้างแถบสำหรับ C # 4 บางทีอาจจะเป็นภาษาในอนาคตที่เป็นสมมุติฐาน

Update (2014):?.ผู้ประกอบการอยู่ในขณะนี้มีการวางแผนสำหรับการเปิดตัวคอมไพเลอร์โรสลินถัดไป โปรดทราบว่ายังคงมีข้อถกเถียงเกี่ยวกับการวิเคราะห์วากยสัมพันธ์และความหมายของตัวดำเนินการ

อัปเดต (กรกฎาคม 2015): Visual Studio 2015 ได้รับการเผยแพร่และมาพร้อมกับคอมไพเลอร์ C # ที่สนับสนุนตัวดำเนินการเงื่อนไขว่าง?.และ?[] .


10
หากไม่มีจุดจะกลายเป็นความคลุมเครือทางไวยากรณ์กับตัวดำเนินการเงื่อนไข (A? B: C) เราพยายามหลีกเลี่ยงโครงสร้างศัพท์ที่ทำให้เราต้อง "มองไปข้างหน้า" โดยพลการในกระแสโทเค็น (แม้ว่าน่าเสียดายที่มีโครงสร้างดังกล่าวอยู่แล้วใน C # เราไม่ต้องการเพิ่มอีกแล้ว)
Eric Lippert

33
@Ian: ปัญหานี้เป็นอย่างมากที่พบบ่อย นี่เป็นหนึ่งในคำขอบ่อยที่สุดที่เราได้รับ
Eric Lippert

7
@ Ian: ฉันชอบใช้รูปแบบวัตถุว่างเมื่อเป็นไปได้ แต่คนส่วนใหญ่ไม่มีความหรูหราในการทำงานกับโมเดลวัตถุที่พวกเขาออกแบบเอง โมเดลอ็อบเจ็กต์ที่มีอยู่จำนวนมากใช้ null ดังนั้นนั่นคือโลกที่เราต้องอยู่ด้วย
Eric Lippert

12
@ จอห์น: เราได้รับคำขอคุณลักษณะนี้เกือบทั้งหมดจากโปรแกรมเมอร์ที่มีประสบการณ์มากที่สุดของเรา MVPs ขอนี้ตลอดเวลา แต่ฉันเข้าใจว่าความคิดเห็นแตกต่างกันไป หากคุณต้องการให้ข้อเสนอแนะในการออกแบบภาษาที่สร้างสรรค์นอกเหนือจากคำวิจารณ์ของคุณเรายินดีที่จะพิจารณา
Eric Lippert

28
@lazyberezovsky: ฉันไม่เคยเข้าใจสิ่งที่เรียกว่า "กฎหมาย" ของ Demeter; ประการแรกดูเหมือนว่าจะถูกเรียกว่า "คำแนะนำของ Demeter" อย่างถูกต้องมากขึ้น และประการที่สองผลของการ "เข้าถึงสมาชิกเพียงคนเดียว" ไปสู่ข้อสรุปเชิงตรรกะคือ "วัตถุของพระเจ้า" ซึ่งทุก ๆ วัตถุจำเป็นต้องทำทุกอย่างเพื่อลูกค้าทุกคนแทนที่จะสามารถแจกสิ่งของที่รู้วิธีทำในสิ่งที่ลูกค้า ต้องการ ฉันชอบสิ่งที่ตรงกันข้ามกับกฎแห่งดีมีเตอร์: ทุกวัตถุแก้ปัญหาได้ดีเล็กน้อยและหนึ่งในวิธีแก้ปัญหาเหล่านั้นอาจเป็น "นี่คืออีกหนึ่งวัตถุที่แก้ปัญหาของคุณได้ดีกว่า"
Eric Lippert

27

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

ดังนั้นฉันจึงสร้างวิธีการขยายซึ่งจะช่วยให้คุณสามารถเขียน:

var berries = cake.IfNotNull(c => c.Frosting.Berries);

สิ่งนี้จะส่งคืนผลเบอร์รี่หากไม่มีส่วนใดของนิพจน์ที่เป็นโมฆะ หากพบค่าว่างระบบจะส่งคืนค่าว่าง มีข้อแม้บางประการแม้ว่าในเวอร์ชันปัจจุบันจะใช้งานได้กับการเข้าถึงของสมาชิกแบบธรรมดาเท่านั้นและใช้ได้กับ. NET Framework 4 เท่านั้นเนื่องจากใช้เมธอด MemberExpression.Update ซึ่งเป็นเวอร์ชันใหม่ใน v4 นี่คือรหัสสำหรับวิธีการขยาย IfNotNull:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace dr.IfNotNullOperator.PoC
{
    public static class ObjectExtensions
    {
        public static TResult IfNotNull<TArg,TResult>(this TArg arg, Expression<Func<TArg,TResult>> expression)
        {
            if (expression == null)
                throw new ArgumentNullException("expression");

            if (ReferenceEquals(arg, null))
                return default(TResult);

            var stack = new Stack<MemberExpression>();
            var expr = expression.Body as MemberExpression;
            while(expr != null)
            {
                stack.Push(expr);
                expr = expr.Expression as MemberExpression;
            } 

            if (stack.Count == 0 || !(stack.Peek().Expression is ParameterExpression))
                throw new ApplicationException(String.Format("The expression '{0}' contains unsupported constructs.",
                                                             expression));

            object a = arg;
            while(stack.Count > 0)
            {
                expr = stack.Pop();
                var p = expr.Expression as ParameterExpression;
                if (p == null)
                {
                    p = Expression.Parameter(a.GetType(), "x");
                    expr = expr.Update(p);
                }
                var lambda = Expression.Lambda(expr, p);
                Delegate t = lambda.Compile();                
                a = t.DynamicInvoke(a);
                if (ReferenceEquals(a, null))
                    return default(TResult);
            }

            return (TResult)a;            
        }
    }
}

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

ฉันแน่ใจว่าสิ่งนี้สามารถขยายได้เพื่อให้รองรับนิพจน์อื่นที่ไม่ใช่ MemberExpression พิจารณาว่านี่เป็นรหัสพิสูจน์แนวคิดและโปรดจำไว้ว่าจะมีการลงโทษด้านประสิทธิภาพโดยการใช้ (ซึ่งอาจไม่สำคัญในหลาย ๆ กรณี แต่อย่าใช้แบบวนซ้ำ :-))


ฉันประทับใจในทักษะแลมด้าของคุณ :) อย่างไรก็ตามไวยากรณ์ดูเหมือนจะซับซ้อนกว่าที่ต้องการเล็กน้อยอย่างน้อยที่สุดสำหรับสถานการณ์ if-statement
Homde

เจ๋ง แต่มันทำงานเหมือนโค้ดมากกว่า 100 เท่า if .. &&. จะคุ้มค่าก็ต่อเมื่อมันยังคงรวบรวมไว้เป็น if .. &&.
Monstieur

1
อ่าแล้วฉันเห็นDynamicInvokeตรงนั้น ฉันหลีกเลี่ยงสิ่งนั้นอย่างเคร่งครัด :)
nawfal

24

ฉันพบว่าส่วนขยายนี้มีประโยชน์มากสำหรับสถานการณ์ที่ซ้อนกัน

public static R Coal<T, R>(this T obj, Func<T, R> f)
    where T : class
{
    return obj != null ? f(obj) : default(R);
}

เป็นความคิดที่ฉันได้มาจากตัวดำเนินการเชื่อมต่อว่างใน C # และ T-SQL สิ่งที่ดีคือประเภทการส่งคืนจะเป็นประเภทผลตอบแทนของคุณสมบัติภายในเสมอ

ด้วยวิธีนี้คุณสามารถทำได้:

var berries = cake.Coal(x => x.frosting).Coal(x => x.berries);

... หรือรูปแบบเล็กน้อยข้างต้น:

var berries = cake.Coal(x => x.frosting, x => x.berries);

มันไม่ใช่ไวยากรณ์ที่ดีที่สุดที่ฉันรู้ แต่มันใช้งานได้


ทำไม "ถ่านหิน" ถึงดูน่าขนลุกอย่างยิ่ง ;) อย่างไรก็ตามตัวอย่างของคุณจะล้มเหลวหากฟรอสติ้งเป็นโมฆะ ควรมีลักษณะดังนี้: var Berries = cake.NullSafe (c => c.Frosting.NullSafe (f => f.Berries));
Robert Giesecke

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

ชื่อการรวมตัวกันหรือการรวมตัวกันถูกนำมาจาก T-SQL นั่นคือสิ่งที่ฉันได้รับความคิดแรก IfNotNull หมายความว่ามีบางสิ่งเกิดขึ้นหากไม่เป็นโมฆะไม่ว่าสิ่งนั้นคืออะไรจะไม่อธิบายด้วยการเรียกเมธอด IfNotNull ถ่านหินเป็นชื่อแปลก ๆ แต่นี่เป็นวิธีการแปลก ๆ ที่ควรค่าแก่การสังเกต
John Leidegren

ชื่อที่ดีที่สุดสำหรับชื่อนี้คือ "ReturnIfNotNull" หรือ "ReturnOrDefault"
John Leidegren

@flq +1 ... ในโครงการของเราเรียกอีกอย่างว่า IfNotNull :)
Marc Sigrist

16

นอกเหนือจากการละเมิดกฎแห่ง Demeter ดังที่ Mehrdad Afshari ได้ชี้ให้เห็นแล้วดูเหมือนว่าสำหรับฉันแล้วคุณต้องมี "การตรวจสอบค่าว่างเชิงลึก" สำหรับตรรกะในการตัดสินใจ

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


ไม่วัตถุประสงค์ -c อนุญาตให้ส่งข้อความไปยังวัตถุว่างและส่งคืนค่าเริ่มต้นที่เหมาะสมหากจำเป็น ไม่มีปัญหาที่นั่น
Johannes Rudolph

2
ใช่. นั่นคือประเด็น โดยพื้นฐานแล้วคุณจะเลียนแบบพฤติกรรม ObjC ด้วย Null Object Pattern
Mehrdad Afshari

10

อัปเดต:ตั้งแต่ Visual Studio 2015 คอมไพลเลอร์ C # (ภาษาเวอร์ชัน 6) ตอนนี้รู้จักตัว?.ดำเนินการซึ่งทำให้ "การตรวจสอบค่า null เชิงลึก" เป็นเรื่องง่าย ดูรายละเอียดคำตอบนี้

นอกเหนือจากการออกแบบโค้ดของคุณใหม่เช่นเดียว กับคำตอบที่ถูกลบนี้แนะนำตัวเลือกอื่น (แม้ว่าจะแย่มาก) คือการใช้try…catchบล็อกเพื่อดูว่ามีอะไรNullReferenceExceptionเกิดขึ้นบ้างในระหว่างการค้นหาคุณสมบัติเชิงลึกนั้นหรือไม่

try
{
    var x = cake.frosting.berries.loader;
    ...
}
catch (NullReferenceException ex)
{
    // either one of cake, frosting, or berries was null
    ...
}

โดยส่วนตัวแล้วฉันจะไม่ทำสิ่งนี้ด้วยเหตุผลต่อไปนี้:

  • มันดูไม่ดี
  • ใช้การจัดการข้อยกเว้นซึ่งควรกำหนดเป้าหมายสถานการณ์พิเศษไม่ใช่สิ่งที่คุณคาดว่าจะเกิดขึ้นบ่อยครั้งในระหว่างการดำเนินการปกติ
  • NullReferenceExceptions ไม่ควรถูกจับอย่างชัดเจน (ดูคำถามนี้)

ดังนั้นจึงเป็นไปได้โดยใช้วิธีการขยายบางอย่างหรือจะเป็นคุณลักษณะของภาษา [... ]

เกือบจะแน่นอนว่าจะต้องเป็นคุณลักษณะของภาษา (ซึ่งมีอยู่ใน C # 6 ในรูปแบบของ.?และ?[]ตัวดำเนินการ) เว้นแต่ C # จะมีการประเมินแบบขี้เกียจที่ซับซ้อนกว่านี้อยู่แล้วหรือเว้นแต่คุณต้องการใช้การสะท้อน (ซึ่งอาจไม่ใช่ ความคิดที่ดีด้วยเหตุผลด้านประสิทธิภาพและประเภทความปลอดภัย)

เนื่องจากไม่มีวิธีเพียงแค่ส่งผ่านcake.frosting.berries.loaderไปยังฟังก์ชัน (จะได้รับการประเมินและโยนข้อยกเว้นการอ้างอิงว่าง) คุณจะต้องใช้วิธีการค้นหาทั่วไปด้วยวิธีต่อไปนี้: ใช้ในวัตถุและชื่อของคุณสมบัติในการ ค้นหา:

static object LookupProperty( object startingPoint, params string[] lookupChain )
{
    // 1. if 'startingPoint' is null, return null, or throw an exception.
    // 2. recursively look up one property/field after the other from 'lookupChain',
    //    using reflection.
    // 3. if one lookup is not possible, return null, or throw an exception.
    // 3. return the last property/field's value.
}

...

var x = LookupProperty( cake, "frosting", "berries", "loader" );

(หมายเหตุ: แก้ไขโค้ดแล้ว)

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

[... ] หรือมันเป็นเพียงความคิดที่ไม่ดี?

ฉันจะอยู่กับ:

if (cake != null && cake.frosting != null && ...) ...

หรือไปกับคำตอบข้างต้นโดย Mehrdad Afshari


PS:ย้อนกลับไปตอนที่ฉันเขียนคำตอบนี้เห็นได้ชัดว่าฉันไม่ได้พิจารณาต้นไม้นิพจน์สำหรับฟังก์ชันแลมด้า ดูเช่นคำตอบของ @driis สำหรับวิธีแก้ปัญหาในทิศทางนี้ นอกจากนี้ยังขึ้นอยู่กับประเภทของการสะท้อนกลับดังนั้นจึงอาจทำงานได้ไม่ดีเท่ากับวิธีแก้ปัญหาที่ง่ายกว่า ( if (… != null & … != null) …) แต่อาจตัดสินได้ดีกว่าจากมุมมองของไวยากรณ์


2
ฉันไม่รู้ว่าทำไมสิ่งนี้จึงถูกลดคะแนนฉันได้โหวตเพิ่มยอดคงเหลือ: คำตอบถูกต้องและนำเสนอแง่มุมใหม่ ๆ (และกล่าวถึงข้อเสียของโซลูชันนี้อย่างชัดเจน ... )
MartinStettner

"คำตอบข้างต้นของ Mehrdad Afshari" อยู่ที่ไหน
Marson Mao

1
@MarsonMao: คำตอบนั้นถูกลบไปแล้วในระหว่างนี้ (คุณยังสามารถอ่านได้หากอันดับ SO ของคุณสูงเพียงพอ) ขอบคุณที่ชี้ให้เห็นข้อผิดพลาดของฉัน: ฉันควรอ้างถึงคำตอบอื่น ๆ โดยใช้ไฮเปอร์ลิงก์ไม่ใช่ใช้คำเช่น "ดูด้านบน" / "ดูด้านล่าง" (เนื่องจาก คำตอบไม่ปรากฏในลำดับที่ตายตัว) ฉันได้อัปเดตคำตอบแล้ว
stakx - ไม่ร่วมให้ข้อมูลอีกต่อไป

5

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

NullCoalesce ด้านล่างทำเพียงแค่นั้นมันจะส่งคืนนิพจน์แลมบ์ดาใหม่ด้วยการตรวจสอบค่าว่างและส่งคืนค่าเริ่มต้น (TResult) ในกรณีที่เส้นทางใด ๆ เป็นโมฆะ

ตัวอย่าง:

NullCoalesce((Process p) => p.StartInfo.FileName)

จะส่งคืนนิพจน์

(Process p) => (p != null && p.StartInfo != null ? p.StartInfo.FileName : default(string));

รหัส:

    static void Main(string[] args)
    {
        var converted = NullCoalesce((MethodInfo p) => p.DeclaringType.Assembly.Evidence.Locked);
        var converted2 = NullCoalesce((string[] s) => s.Length);
    }

    private static Expression<Func<TSource, TResult>> NullCoalesce<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
    {
        var test = GetTest(lambdaExpression.Body);
        if (test != null)
        {
            return Expression.Lambda<Func<TSource, TResult>>(
                Expression.Condition(
                    test,
                    lambdaExpression.Body,
                    Expression.Default(
                        typeof(TResult)
                    )
                ),
                lambdaExpression.Parameters
            );
        }
        return lambdaExpression;
    }

    private static Expression GetTest(Expression expression)
    {
        Expression container;
        switch (expression.NodeType)
        {
            case ExpressionType.ArrayLength:
                container = ((UnaryExpression)expression).Operand;
                break;
            case ExpressionType.MemberAccess:
                if ((container = ((MemberExpression)expression).Expression) == null)
                {
                    return null;
                }
                break;
            default:
                return null;
        }
        var baseTest = GetTest(container);
        if (!container.Type.IsValueType)
        {
            var containerNotNull = Expression.NotEqual(
                container,
                Expression.Default(
                    container.Type
                )
            );
            return (baseTest == null ?
                containerNotNull :
                Expression.AndAlso(
                    baseTest,
                    containerNotNull
                )
            );
        }
        return baseTest;
    }

4

ทางเลือกหนึ่งคือใช้ Null Object Patten ดังนั้นแทนที่จะมีค่าว่างเมื่อคุณไม่มีเค้กคุณมี NullCake ที่ส่งคืน NullFosting เป็นต้นขออภัยฉันอธิบายเรื่องนี้ได้ไม่ดีนัก แต่มีคนอื่นดู


3

ฉันมักต้องการไวยากรณ์ที่ง่ายกว่านี้ด้วยเช่นกัน! จะได้รับน่าเกลียดโดยเฉพาะอย่างยิ่งเมื่อคุณมีวิธีการกลับค่าที่อาจเป็นโมฆะแล้วเพราะคุณต้องตัวแปรพิเศษ (ตัวอย่างเช่น: cake.frosting.flavors.FirstOrDefault().loader)

อย่างไรก็ตามนี่เป็นทางเลือกที่ดีงามที่ฉันใช้: สร้างวิธีตัวช่วย Null-Safe-Chain ฉันรู้ว่านี่ค่อนข้างคล้ายกับคำตอบของ @John ด้านบน (ด้วยCoalวิธีการขยาย) แต่ฉันพบว่ามันตรงกว่าและพิมพ์น้อยลง นี่คือสิ่งที่ดูเหมือน:

var loader = NullSafe.Chain(cake, c=>c.frosting, f=>f.berries, b=>b.loader);

นี่คือการใช้งาน:

public static TResult Chain<TA,TB,TC,TResult>(TA a, Func<TA,TB> b, Func<TB,TC> c, Func<TC,TResult> r) 
where TA:class where TB:class where TC:class {
    if (a == null) return default(TResult);
    var B = b(a);
    if (B == null) return default(TResult);
    var C = c(B);
    if (C == null) return default(TResult);
    return r(C);
}

ฉันยังสร้างโอเวอร์โหลดหลายตัว (ด้วยพารามิเตอร์ 2 ถึง 6 ตัว) เช่นเดียวกับโอเวอร์โหลดที่อนุญาตให้เชนลงท้ายด้วยประเภทค่าหรือค่าเริ่มต้น มันใช้ได้ดีกับฉันจริงๆ!


1

มีโครงการอาจจะ codeplexที่ Implements Maybe หรือ IfNotNull โดยใช้ lambdas สำหรับนิพจน์ลึกใน C #

ตัวอย่างการใช้งาน:

int? CityId= employee.Maybe(e=>e.Person.Address.City);

ลิงก์ถูกแนะนำในคำถามที่คล้ายกันจะตรวจสอบค่า null ในนิพจน์ lambda ได้อย่างไร?


1

ตามที่แนะนำไว้ในคำตอบของJohn Leidegrenแนวทางหนึ่งในการแก้ไขปัญหานี้คือการใช้วิธีการขยายและผู้รับมอบสิทธิ์ การใช้อาจมีลักษณะดังนี้:

int? numberOfBerries = cake
    .NullOr(c => c.Frosting)
    .NullOr(f => f.Berries)
    .NullOr(b => b.Count());

การนำไปใช้งานนั้นยุ่งเหยิงเนื่องจากคุณจำเป็นต้องทำให้มันใช้งานได้กับประเภทค่าประเภทการอ้างอิงและประเภทค่าที่เป็นโมฆะ คุณสามารถค้นหาการดำเนินการแล้วเสร็จในTimwi 's คำตอบจะเป็นวิธีที่เหมาะสมในการตรวจสอบค่า null? .


1

หรือคุณอาจใช้การสะท้อนกลับ :)

ฟังก์ชั่นการสะท้อนแสง:

public Object GetPropValue(String name, Object obj)
    {
        foreach (String part in name.Split('.'))
        {
            if (obj == null) { return null; }

            Type type = obj.GetType();
            PropertyInfo info = type.GetProperty(part);
            if (info == null) { return null; }

            obj = info.GetValue(obj, null);
        }
        return obj;
    }

การใช้งาน:

object test1 = GetPropValue("PropertyA.PropertyB.PropertyC",obj);

กรณีของฉัน (ส่งคืน DBNull.Value แทนค่าว่างในฟังก์ชันการสะท้อน):

cmd.Parameters.AddWithValue("CustomerContactEmail", GetPropValue("AccountingCustomerParty.Party.Contact.ElectronicMail.Value", eInvoiceType));

1

ลองใช้รหัสนี้:

    /// <summary>
    /// check deep property
    /// </summary>
    /// <param name="obj">instance</param>
    /// <param name="property">deep property not include instance name example "A.B.C.D.E"</param>
    /// <returns>if null return true else return false</returns>
    public static bool IsNull(this object obj, string property)
    {
        if (string.IsNullOrEmpty(property) || string.IsNullOrEmpty(property.Trim())) throw new Exception("Parameter : property is empty");
        if (obj != null)
        {
            string[] deep = property.Split('.');
            object instance = obj;
            Type objType = instance.GetType();
            PropertyInfo propertyInfo;
            foreach (string p in deep)
            {
                propertyInfo = objType.GetProperty(p);
                if (propertyInfo == null) throw new Exception("No property : " + p);
                instance = propertyInfo.GetValue(instance, null);
                if (instance != null)
                    objType = instance.GetType();
                else
                    return true;
            }
            return false;
        }
        else
            return true;
    }

0

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

var color = Dis.OrDat<string>(() => cake.frosting.berries.color, "blue");


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace DeepNullCoalescence
{
  public static class Dis
  {
    public static T OrDat<T>(Expression<Func><T>> expr, T dat)
    {
      try
      {
        var func = expr.Compile();
        var result = func.Invoke();
        return result ?? dat; //now we can coalesce
      }
      catch (NullReferenceException)
      {
        return dat;
      }
    }
  }
}

อ่านบล็อกโพสต์ฉบับเต็มได้ที่นี่บล็อกโพสต์เต็มได้ที่นี่

เพื่อนเดียวกันยังแนะนำให้คุณดูนี้


3
ทำไมต้องกังวลกับExpressionถ้าคุณกำลังจะรวบรวมและจับ? เพียงใช้ไฟล์Func<T>.
Scott Rippey

0

ฉันแก้ไขโค้ดเล็กน้อยจากที่นี่เพื่อให้ใช้งานได้กับคำถามที่ถาม:

public static class GetValueOrDefaultExtension
{
    public static TResult GetValueOrDefault<TSource, TResult>(this TSource source, Func<TSource, TResult> selector)
    {
        try { return selector(source); }
        catch { return default(TResult); }
    }
}

และใช่นี่อาจไม่ใช่ทางออกที่ดีที่สุดเนื่องจากผลกระทบด้านประสิทธิภาพการลอง / จับ แต่ใช้งานได้:>

การใช้งาน:

var val = cake.GetValueOrDefault(x => x.frosting.berries.loader);

0

ในกรณีที่คุณต้องทำสิ่งนี้ให้ทำดังนี้:

การใช้

Color color = someOrder.ComplexGet(x => x.Customer.LastOrder.Product.Color);

หรือ

Color color = Complex.Get(() => someOrder.Customer.LastOrder.Product.Color);

การใช้งานคลาส Helper

public static class Complex
{
    public static T1 ComplexGet<T1, T2>(this T2 root, Func<T2, T1> func)
    {
        return Get(() => func(root));
    }

    public static T Get<T>(Func<T> func)
    {
        try
        {
            return func();
        }
        catch (Exception)
        {
            return default(T);
        }
    }
}

-3

ฉันชอบแนวทางของ Objective-C:

"ภาษา Objective-C ใช้แนวทางอื่นในการแก้ปัญหานี้และไม่เรียกใช้เมธอดบน nil แต่จะคืนค่า nil สำหรับการเรียกใช้ทั้งหมด"

if (cake.frosting.berries != null) 
{
    var str = cake.frosting.berries...;
}

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