C # cara elegan untuk memeriksa apakah properti properti adalah null

97

Dalam C #, katakanlah Anda ingin menarik nilai dari PropertyC dalam contoh ini dan ObjectA, PropertyA dan PropertyB semuanya bisa menjadi null.

ObjectA.PropertyA.PropertyB.PropertyC

Bagaimana saya bisa mendapatkan PropertyC dengan aman dengan jumlah kode yang paling sedikit?

Sekarang saya akan memeriksa:

if(ObjectA != null && ObjectA.PropertyA !=null && ObjectA.PropertyA.PropertyB != null)
{
    // safely pull off the value
    int value = objectA.PropertyA.PropertyB.PropertyC;
}

Akan menyenangkan untuk melakukan sesuatu yang lebih seperti ini (pseudo-code).

int value = ObjectA.PropertyA.PropertyB ? ObjectA.PropertyA.PropertyB : defaultVal;

Mungkin lebih jauh lagi runtuh dengan operator penggabungan nol.

EDIT Awalnya saya mengatakan contoh kedua saya seperti js, tetapi saya mengubahnya menjadi psuedo-code karena itu dengan benar menunjukkan bahwa itu tidak akan berfungsi di js.

Jon Kragh
sumber
saya tidak melihat bagaimana contoh js Anda bekerja. Anda harus mendapatkan kesalahan "objek diharapkan" setiap kali ObjectAatau PropertyAnol.
lincolnk

Jawaban:

117

Dalam C # 6 Anda dapat menggunakan Operator Bersyarat Null . Jadi tes aslinya adalah:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;
Phillip Ngan
sumber
2
bisakah Anda menjelaskan apa yang dilakukannya? Apa yang valuesama dengan jika PropertyCnol? atau jika PropertyBnol? bagaimana jika Object Anol?
Kolob Canyon
1
Jika SALAH SATU dari properti ini adalah null, maka seluruh pernyataan akan kembali sebagai null. Dimulai dari kiri ke kanan. Tanpa gula sintaksis ini setara dengan serangkaian pernyataan if di mana if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......dengan ekspresi terakhir akhirnya menjadiif(propertyZ != null) { value = propertyZ }
DetectivePikachu
27

Metode Perpanjangan Pendek:

public static TResult IfNotNull<TInput, TResult>(this TInput o, Func<TInput, TResult> evaluator)
  where TResult : class where TInput : class
{
  if (o == null) return null;
  return evaluator(o);
}

Menggunakan

PropertyC value = ObjectA.IfNotNull(x => x.PropertyA).IfNotNull(x => x.PropertyB).IfNotNull(x => x.PropertyC);

Metode ekstensi sederhana ini dan banyak lagi yang dapat Anda temukan di http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/

EDIT:

Setelah menggunakannya sebentar saya pikir nama yang tepat untuk metode ini harus IfNotNull () bukan asli With ().

Krzysztof Morcinek
sumber
15

Bisakah Anda menambahkan metode ke kelas Anda? Jika tidak, apakah Anda pernah berpikir untuk menggunakan metode ekstensi? Anda dapat membuat metode ekstensi untuk tipe objek Anda yang disebutGetPropC() .

Contoh:

public static class MyExtensions
{
    public static int GetPropC(this MyObjectType obj, int defaltValue)
    {
        if (obj != null && obj.PropertyA != null & obj.PropertyA.PropertyB != null)
            return obj.PropertyA.PropertyB.PropertyC;
        return defaltValue;
    }
}

Pemakaian:

int val = ObjectA.GetPropC(0); // will return PropC value, or 0 (defaltValue)

Omong-omong, ini mengasumsikan Anda menggunakan .NET 3 atau lebih tinggi.

Sam
sumber
11

Cara Anda melakukannya benar.

Anda bisa menggunakan trik seperti yang dijelaskan di sini , menggunakan ekspresi Linq:

int value = ObjectA.NullSafeEval(x => x.PropertyA.PropertyB.PropertyC, 0);

Tetapi jauh lebih lambat daripada memeriksa setiap properti secara manual ...

Thomas Levesque
sumber
10

Refactor untuk mematuhi Hukum Demeter

rtalbot.dll
sumber
Saya tidak menganggap grafik objek yang hanya sedalam tiga tingkat perlu refactoring ketika Anda hanya membaca properti. Saya setuju jika OP ingin memanggil metode pada objek yang direferensikan melalui PropertyC tetapi tidak ketika itu adalah properti yang hanya perlu memeriksa null sebelum membaca. Dalam contoh ini, bisa sesederhana Customer.Address.Country dimana Negara bisa menjadi tipe referensi seperti KeyValuePair. Bagaimana Anda refactor ini untuk mencegah perlunya pemeriksaan referensi null?
Darren Lewis
Contoh OP sebenarnya 4 dalam. Saran saya bukan untuk menghapus pemeriksaan ref null tetapi untuk menemukan mereka di objek yang paling mungkin untuk dapat menanganinya dengan benar. Seperti kebanyakan "aturan praktis" ada pengecualian, tetapi saya tidak yakin ini salah satunya. Bisakah kita setuju untuk tidak setuju?
rtalbot
3
Saya setuju dengan @rtalbot (meskipun, dalam keadilan @Daz Lewis mengusulkan contoh 4-mendalam, karena item terakhir adalah KeyValuePair). Jika ada sesuatu yang mengacaukan objek Pelanggan, maka saya tidak melihat bisnis apa yang dilihatnya melalui heirarki objek Alamat. Misalkan Anda kemudian memutuskan bahwa KeyValuePair bukanlah ide yang bagus untuk properti Country. Dalam hal ini, kode setiap orang harus diubah. Itu bukan desain yang bagus.
Jeffrey L Whitledge
10

Pembaruan 2014: C # 6 memiliki ?.berbagai operator baru yang disebut 'navigasi aman' atau 'penyebaran nol'

parent?.child

Baca http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-sometimes-called-the-safe-navigation-operator.aspx untuk detailnya

Ini telah lama menjadi permintaan yang sangat populer https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d

Kolonel Panik
sumber
8

Anda jelas mencari Nullable Monad :

string result = new A().PropertyB.PropertyC.Value;

menjadi

string result = from a in new A()
                from b in a.PropertyB
                from c in b.PropertyC
                select c.Value;

Ini mengembalikan null, jika salah satu properti nullable adalah null; jika tidak, nilai Value.

class A { public B PropertyB { get; set; } }
class B { public C PropertyC { get; set; } }
class C { public string Value { get; set; } }

Metode ekstensi LINQ:

public static class NullableExtensions
{
    public static TResult SelectMany<TOuter, TInner, TResult>(
        this TOuter source,
        Func<TOuter, TInner> innerSelector,
        Func<TOuter, TInner, TResult> resultSelector)
        where TOuter : class
        where TInner : class
        where TResult : class
    {
        if (source == null) return null;
        TInner inner = innerSelector(source);
        if (inner == null) return null;
        return resultSelector(source, inner);
    }
}
dtb
sumber
Mengapa metode ekstensi ada di sini? Itu tidak digunakan.
Mladen Mihajlovic
1
@MladenMihajlovic: SelectManymetode ekstensi digunakan oleh from ... in ... from ... in ...sintaks.
dtb
5

Dengan asumsi Anda memiliki nilai kosong dari tipe satu pendekatan akan menjadi ini:

var x = (((objectA ?? A.Empty).PropertyOfB ?? B.Empty).PropertyOfC ?? C.Empty).PropertyOfString;

Saya penggemar berat C # tapi hal yang sangat bagus di Java baru (1.7?) Adalah.? operator:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;
Hanya metaprogrammer lainnya
sumber
1
Apakah benar-benar akan ada di Java 1.7? Sudah lama diminta di C #, tapi saya ragu itu akan pernah terjadi ...
Thomas Levesque
Sayangnya saya tidak memiliki nilai kosong. Sintaks Java itu terlihat manis! Saya akan memilih ini, hanya karena saya ingin sintaks itu!
Jon Kragh
3
Thomas: Terakhir kali saya memeriksa tech.puredanger.com/java7, itu menyiratkan bahwa Java akan mendapatkannya. Tetapi sekarang ketika saya memeriksanya kembali dikatakan: Penanganan yang tidak aman: TIDAK. Jadi saya mencabut pernyataan saya dan menggantinya dengan yang baru: Itu diusulkan untuk Java 1.7 tetapi tidak berhasil.
Hanya metaprogrammer lain
Pendekatan tambahan adalah yang digunakan oleh monad.net
Hanya metaprogrammer lain
1
Sepertinya?. operator ada di Visual Studio 2015 https://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward
4

Kode ini adalah "jumlah kode paling sedikit", tetapi bukan praktik terbaik:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}
Boris Modylevsky
sumber
1
Saya telah melihat kode seperti ini banyak dan mengabaikan hilangnya kinerja, masalah terbesar adalah bahwa itu mempersulit debugging karena pengecualian sebenarnya tenggelam dalam jutaan pengecualian ref null yang tidak berguna.
Hanya metaprogrammer lain
Terkadang lucu membaca jawaban saya sendiri setelah 3 tahun. Saya pikir, saya akan menjawab berbeda hari ini. Saya akan mengatakan bahwa kode tersebut melanggar hukum Demeter dan saya akan merekomendasikan refactoring sehingga tidak akan.
Boris Modylevsky
1
Mulai hari ini, 7 tahun setelah jawaban asli, saya akan bergabung dengan @Phillip Ngan dan akan menggunakan C # 6 dengan sintaks berikut: int? nilai = objekA? .PropertyA? .PropertyB? .PropertyC;
Boris Modylevsky
4

Ketika saya perlu melakukan panggilan berantai seperti itu, saya mengandalkan metode helper yang saya buat, TryGet ():

    public static U TryGet<T, U>(this T obj, Func<T, U> func)
    {
        return obj.TryGet(func, default(U));
    }

    public static U TryGet<T, U>(this T obj, Func<T, U> func, U whenNull)
    {
        return obj == null ? whenNull : func(obj);
    }

Dalam kasus Anda, Anda akan menggunakannya seperti ini:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);
Emanuel
sumber
Saya tidak berpikir kode ini berfungsi. Apa jenis defaultVal? var p = Orang baru (); Assert.AreEqual (p.TryGet (x => x.FirstName) .TryGet (x => x.LastName) .TryGet (x => x.NickName, "foo"), "foo");
Keith
Contoh yang saya tulis harus dibaca seperti ini: ObjectA.PropertyA.PropertyB.PropertyC. Kode Anda tampaknya mencoba memuat properti bernama "LastName" dari "FirstName", yang bukan penggunaan yang dimaksudkan. Contoh yang lebih tepat akan menjadi seperti: var postcode = person.TryGet (p => p.Address) .TryGet (p => p.Postcode); Omong-omong, metode pembantu TryGet () saya sangat mirip dengan fitur baru di C # 6.0 - operator bersyarat null. Penggunaannya akan seperti ini: var postcode = person? .Address? .Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
Emanuel
3

Saya melihat sesuatu di C # 6.0 baru, ini dengan menggunakan '?' alih-alih memeriksa

misalnya daripada menggunakan

if (Person != null && Person.Contact!=null && Person.Contact.Address!= null && Person.Contact.Address.City != null)
{ 
  var city = person.contact.address.city;
}

Anda cukup menggunakan

var city = person?.contact?.address?.city;

Saya harap ini membantu seseorang.


MEMPERBARUI:

Anda bisa melakukan seperti ini sekarang

 var city = (Person != null)? 
           ((Person.Contact!=null)? 
              ((Person.Contact.Address!= null)?
                      ((Person.Contact.Address.City!=null)? 
                                 Person.Contact.Address.City : null )
                       :null)
               :null)
            : null;
iYazee6
sumber
1

Anda bisa melakukan ini:

class ObjectAType
{
    public int PropertyC
    {
        get
        {
            if (PropertyA == null)
                return 0;
            if (PropertyA.PropertyB == null)
                return 0;
            return PropertyA.PropertyB.PropertyC;
        }
    }
}



if (ObjectA != null)
{
    int value = ObjectA.PropertyC;
    ...
}

Atau lebih baik lagi:

private static int GetPropertyC(ObjectAType objectA)
{
    if (objectA == null)
        return 0;
    if (objectA.PropertyA == null)
        return 0;
    if (objectA.PropertyA.PropertyB == null)
        return 0;
    return objectA.PropertyA.PropertyB.PropertyC;
}


int value = GetPropertyC(ObjectA);
Jeffrey L. Whitledge
sumber
1

Anda dapat menggunakan ekstensi berikut dan menurut saya ini sangat bagus:

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<TF, TR>(TF t, Func<TF, TR> f)
    where TF : class
{
    return t != null ? f(t) : default(TR);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
    where T1 : class
    where T2 : class
{
    return Get(Get(p1, p2), p3);
}

/// <summary>
/// Simplifies null checking
/// </summary>
public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
    where T1 : class
    where T2 : class
    where T3 : class
{
    return Get(Get(Get(p1, p2), p3), p4);
}

Dan digunakan seperti ini:

int value = Nulify.Get(objectA, x=>x.PropertyA, x=>x.PropertyB, x=>x.PropertyC);
Tony
sumber
0

Saya akan menulis metode Anda sendiri dalam tipe PropertyA (atau metode ekstensi jika bukan tipe Anda) menggunakan pola yang mirip dengan tipe Nullable.

class PropertyAType
{
   public PropertyBType PropertyB {get; set; }

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}
Steve Danner
sumber
Nah, dalam hal ini, jelas PropertyB tidak akan pernah bisa menjadi null.
rekursif
0

Baru saja menemukan postingan ini.

Beberapa waktu lalu saya memberikan saran tentang Visual Studio Connect tentang menambahkan file ??? operator .

http://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/4104392-add-as-an-recursive-null-reference-check-opera

Ini akan membutuhkan beberapa pekerjaan dari tim kerangka kerja tetapi tidak perlu mengubah bahasanya tetapi cukup lakukan beberapa keajaiban kompiler. Idenya adalah bahwa kompilator harus mengubah kode ini (sintaks tidak diperbolehkan atm)

string product_name = Order.OrderDetails[0].Product.Name ??? "no product defined";

ke dalam kode ini

Func<string> _get_default = () => "no product defined"; 
string product_name = Order == null 
    ? _get_default.Invoke() 
    : Order.OrderDetails[0] == null 
        ? _get_default.Invoke() 
        : Order.OrderDetails[0].Product == null 
            ? _get_default.Invoke() 
            : Order.OrderDetails[0].Product.Name ?? _get_default.Invoke()

Untuk pemeriksaan null, ini akan terlihat seperti

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;
Jürgen Steinblock
sumber
0

Saya menulis metode yang menerima nilai default, berikut cara menggunakannya:

var teacher = new Teacher();
return teacher.GetProperty(t => t.Name);
return teacher.GetProperty(t => t.Name, "Default name");

Ini kodenya:

public static class Helper
{
    /// <summary>
    /// Gets a property if the object is not null.
    /// var teacher = new Teacher();
    /// return teacher.GetProperty(t => t.Name);
    /// return teacher.GetProperty(t => t.Name, "Default name");
    /// </summary>
    public static TSecond GetProperty<TFirst, TSecond>(this TFirst item1,
        Func<TFirst, TSecond> getItem2, TSecond defaultValue = default(TSecond))
    {
        if (item1 == null)
        {
            return defaultValue;
        }

        return getItem2(item1);
    }
}
Akira Yamamoto
sumber
1
Solusi ini telah disediakan di jawaban lain (berulang kali). Tidak ada alasan sama sekali untuk mempostingnya lagi .
Pelayanan
Saya tidak melihat apa pun yang menerima nilai default.
Akira Yamamoto
Saya menghitung 6 orang lain yang menggunakan nilai default yang ditentukan. Rupanya Anda tidak terlihat terlalu keras.
Pelayanan
0

Ini tidak mungkin.
ObjectA.PropertyA.PropertyBakan gagal jikaObjectA nol karena dereferensi nol, yang merupakan kesalahan.

if(ObjectA != null && ObjectA.PropertyA... bekerja karena korsleting, yaitu ObjectA.PropertyAtidak akan pernah diperiksa jika ObjectAadanull .

Cara pertama Anda mengusulkan adalah yang terbaik dan paling jelas dengan niat. Jika ada, Anda dapat mencoba mendesain ulang tanpa harus bergantung pada begitu banyak null.

DanDan
sumber
-1

Pendekatan ini cukup lurus ke depan setelah Anda mengatasi lambda gobbly-gook:

public static TProperty GetPropertyOrDefault<TObject, TProperty>(this TObject model, Func<TObject, TProperty> valueFunc)  
                                                        where TObject : class
    {
        try
        {
            return valueFunc.Invoke(model);
        }
        catch (NullReferenceException nex)
        {
            return default(TProperty);
        }
    }

Dengan penggunaan yang mungkin terlihat seperti:

ObjectA objectA = null;

Assert.AreEqual(0,objectA.GetPropertyOrDefault(prop=>prop.ObjectB.ObjectB.ObjectC.ID));

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));
BlackjacketMack
sumber
Hanya ingin tahu mengapa seseorang akan memberikan suara negatif 8 tahun setelah saya memberikan tanggapan (yang bertahun-tahun sebelum penggabungan nol C # 6 adalah sesuatu).
BlackjacketMack
-3
var result = nullableproperty ?? defaultvalue;

The ??(null-coalescing operator) berarti jika argumen pertama adalah null, kembalikan yang kedua sebagai gantinya.

Aridane Álamo
sumber
2
Jawaban ini tidak menyelesaikan masalah OP. Bagaimana Anda menerapkan solusi dengan ?? operator ke ObjectA.PropertyA.PropertyB ketika semua bagian dari ekspresi (ObjectA, PropertyA dan PropertyB) dapat menjadi null?
Artemix
Benar, saya pikir saya bahkan tidak membaca pertanyaan itu sama sekali. Bagaimanapun, tidak mungkin tidak ada apa-apa, jangan lakukan itu: P static void Main (string [] args) {a ca = new a (); var default_value = new a () {b = new object ()}; var value = (ca ?? default_value) .b ?? default_value.b; } kelas a {objek publik b = null; }
Aridane Álamo
(ObjectA ?? DefaultMockedAtNull] .PropertyA! = Null? ObjectA.PropertyA.PropertyB: null
Aridane Álamo