C # Bir mülkün özelliğinin boş olup olmadığını kontrol etmenin zarif yolu


100

C # 'da, bu örnekte PropertyC'den bir değer almak istediğinizi ve ObjectA, PropertyA ve PropertyB'nin tümü boş olabilir.

ObjectA.PropertyA.PropertyB.PropertyC

PropertyC'yi en az miktarda kodla güvenli bir şekilde nasıl alabilirim?

Şu anda kontrol ederdim:

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

Bunun gibi bir şey yapmak güzel olurdu (sözde kod).

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

Bir sıfır birleştirme operatörü ile muhtemelen daha da fazla çöktü.

DÜZENLE Başlangıçta ikinci örneğimin js gibi olduğunu söyledim, ancak js'de çalışmayacağına doğru bir şekilde işaret edildiği için psuedo-koduna değiştirdim.


js örneğinizin nasıl çalıştığını anlamıyorum. her ne zaman ObjectAveya PropertyAboş olduğunda bir "nesne bekleniyor" hatası almalısınız .
lincolnk

Yanıtlar:


124

C # 6'da Null Koşullu İşleci kullanabilirsiniz . Yani orijinal test şöyle olacaktır:

int? value = objectA?.PropertyA?.PropertyB?.PropertyC;

2
bunun ne yaptığını açıklayabilir misin? Null ise valueeşittir nedir PropertyC? veya PropertyBnull ise? ya boşsa Object A?
Kolob Canyon

1
Bu özelliklerden HERHANGİ BİRİ boşsa, tüm ifade olarak döndürülür null. Soldan sağa doğru başlar. Sözdizimsel şeker olmadan bu, if(propertyX == null) {value = null} else if (propertyY == null){ value = null} else if......nihai son ifadenin olduğu bir dizi if ifadesine eşdeğerdirif(propertyZ != null) { value = propertyZ }
DetectivePikachu

27

Kısa Uzatma Yöntemi:

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);
}

Kullanma

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

Bu basit uzatma yöntemi ve daha fazlasını http://devtalk.net/csharp/chained-null-checks-and-the-maybe-monad/ adresinde bulabilirsiniz.

DÜZENLE:

Bir an kullandıktan sonra, bu yöntemin doğru adının orijinal With () yerine IfNotNull () olması gerektiğini düşünüyorum .


15

Sınıfınıza bir yöntem ekleyebilir misiniz? Değilse, uzatma yöntemlerini kullanmayı düşündünüz mü? Adlı nesne türünüz için bir uzatma yöntemi oluşturabilirsiniz GetPropC().

Misal:

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;
    }
}

Kullanım:

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

Bu arada, .NET 3 veya üstünü kullandığınızı varsayar.


11

Bunu yapma şeklin doğru.

Sen olabilir anlatılan gibi bir hile kullanmak burada Linq ifadeler kullanarak,:

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

Ancak her mülkün manuel olarak kontrol edilmesi çok daha yavaştır ...


10

Ben sadece özellikleri okurken sadece üç seviye derinliğinde bir nesne grafiğinin yeniden düzenleme gerektirdiğini düşünmüyorum. OP, PropertyC aracılığıyla referans verilen bir nesnede bir yöntem çağırmak istiyorsa, ancak okumadan önce yalnızca boş değerin kontrol edilmesi gereken bir özellik olduğunda değil. Bu örnekte, Country'nin KeyValuePair gibi bir referans türü olabileceği Customer.Address.Country kadar basit olabilir. Boş ref kontrollerine olan ihtiyacı önlemek için bunu nasıl yeniden düzenlersiniz?
Darren Lewis

OP örneği aslında 4 derinliktir. Benim önerim, boş ref denetimlerini kaldırmak değil, bunları düzgün bir şekilde işleyebilecek nesnelerde konumlandırmaktır. Çoğu "pratik kural" gibi istisnalar var ama bunun bir olduğuna ikna olmadım. Aynı fikirde olmamayı kabul edebilir miyiz?
rtalbot

4
@Rtalbot'a katılıyorum (yine de, adalet olarak @Daz Lewis, son öğe bir KeyValuePair olduğu için 4 derinlikli bir örnek öneriyor). Bir Müşteri nesnesiyle uğraşan bir şey varsa, o zaman Adres nesnesi-hiyerarşisi aracılığıyla hangi işe baktığını göremiyorum. Daha sonra bir KeyValuePair'in Ülke mülkü için o kadar da iyi bir fikir olmadığına karar verdiğinizi varsayalım. Bu durumda herkesin kodu değişmek zorundadır. Bu iyi bir tasarım değil.
Jeffrey L Whitledge

10

2014 Güncellemesi: C # 6, ?.'güvenli gezinme' veya 'boş ilerleme' olarak adlandırılan yeni bir operatöre sahip

parent?.child

Ayrıntılar için http://blogs.msdn.com/b/jerrynixon/archive/2014/02/26/at-last-c-is-getting-s bazen-called- the- safe-navigation-operator.aspx okuyun

Bu uzun zamandır oldukça popüler bir istek oldu https://visualstudio.uservoice.com/forums/121579-visual-studio/suggestions/3990187-add-operator-to-c-?tracking_code=594c10a522f8e9bc987ee4a5e2c0b38d


8

Belli ki Nullable Monad'ı arıyorsunuz :

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

olur

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

Bu null, null yapılabilir özelliklerden herhangi biri null ise döndürür ; aksi takdirde, değeri Value.

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

LINQ genişletme yöntemleri:

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);
    }
}

Uzantı yöntemi neden burada? Kullanılmıyor.
Mladen Mihajlovic

1
@MladenMihajlovic: SelectManyuzantı yöntemi from ... in ... from ... in ...sözdizimi tarafından kullanılıyor .
dtb

5

Boş tür değerleriniz olduğunu varsayarsak, bir yaklaşım şu olacaktır:

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

Büyük bir C # hayranıyım ama yeni Java'da (1.7?) Çok güzel bir şey.? Şebeke:

 var x = objectA.?PropertyOfB.?PropertyOfC.?PropertyOfString;

1
Gerçekten Java 1.7'de olacak mı? C # ile uzun zamandır isteniyor, ancak olacağından şüpheliyim ...
Thomas Levesque

Maalesef boş değerlerim yok. Java sözdizimi yine de tatlı görünüyor! Sırf bu sözdizimini istediğim için buna ek oy vereceğim!
Jon Kragh

3
Thomas: Tech.puredanger.com/java7'yi en son kontrol ettiğimde Java'nın bunu alacağını ima ediyordu . Ama şimdi yeniden kontrol ettiğimde şunu söylüyor: Boş güvenli kullanım: HAYIR. Bu yüzden ifademi iptal ettim ve yenisiyle değiştirdim: Java 1.7 için önerildi ancak yapılmadı.
Just another metaprogrammer

Ek bir yaklaşım, monad.net tarafından kullanılan yaklaşımdır
başka bir meta

1
Gibi görünüyor?. operatörü Visual Studio 2015'te https://msdn.microsoft.com/en-us/library/dn986595.aspx
Edward

4

Bu kod "en az kod miktarıdır", ancak en iyi uygulama değildir:

try
{
    return ObjectA.PropertyA.PropertyB.PropertyC;
}
catch(NullReferenceException)
{
     return null;
}

1
Bu kadar çok kod gördüm ve performans kaybını göz ardı ederek en büyük sorun, hata ayıklamayı zorlaştırmasıdır çünkü gerçek istisna milyonlarca gereksiz boş ref istisnasında boğulmaktadır.
2013

Bazen 3 yıl sonra kendi cevabımı okumak eğlenceli oluyor. Sanırım bugün farklı cevap verirdim. Kodun Demeter yasasını ihlal ettiğini söyleyebilirim ve yeniden düzenlemeyi tavsiye ederim ki öyle olmasın.
Boris Modylevsky

1
Bugün itibariyle, orijinal cevaptan 7 yıl sonra, @Phillip Ngan'a katılacak ve aşağıdaki sözdizimiyle C # 6 kullanacaktım: int? değer = nesneA? .PropertyA? .PropertyB? .PropertyC;
Boris Modylevsky

4

Böyle çağrıları zincirlemem gerektiğinde, oluşturduğum bir yardımcı yönteme güveniyorum, 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);
    }

Sizin durumunuzda, bunu şu şekilde kullanırsınız:

    int value = ObjectA
        .TryGet(p => p.PropertyA)
        .TryGet(p => p.PropertyB)
        .TryGet(p => p.PropertyC, defaultVal);

Bu kodun çalıştığını sanmıyorum. DefaultVal türü nedir? var p = new Kişi (); Assert.AreEqual (p. TryGet (x => x.FirstName). TryGet (x => x.LastName). TryGet (x => x.NickName, "foo"), "foo");
Keith

Yazdığım örnek şu şekilde okunmalı: ObjectA.PropertyA.PropertyB.PropertyC. Görünüşe göre kodunuz, amaçlanan kullanım olmayan "FirstName" den "LastName" adlı bir özelliği yüklemeye çalışıyor. Daha doğru bir örnek şuna benzer bir şey olabilir: var postcode = person. TryGet (p => p.Address). TryGet (p => p.Postcode); Bu arada, TryGet () yardımcı yöntemim C # 6.0'daki yeni bir özelliğe - boş koşullu işleç'e çok benziyor. Kullanımı şu şekilde olacaktır: var postcode = person? .Address? .Postcode; msdn.microsoft.com/en-us/magazine/dn802602.aspx
Emanuel

3

Yeni C # 6.0'da bir şey gördüm, bu '?' kontrol etmek yerine

örneğin kullanmak yerine

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

sen sadece kullan

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

Umarım birine yardımcı olmuştur.


GÜNCELLEME:

Şimdi bunu yapabilirsin

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

1

Bunu yapabilirsin:

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;
    ...
}

Ya da daha iyisi şu olabilir:

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);

1

aşağıdaki uzantıyı kullanabilirsiniz ve bunun gerçekten iyi olduğunu düşünüyorum:

/// <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);
}

Ve şu şekilde kullanılır:

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

0

Nullable türüne benzer modeli kullanarak PropertyA türünde (veya türünüz değilse bir uzantı yönteminde) kendi yönteminizi yazardım.

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

   public PropertyBType GetPropertyBOrDefault()
   {
       return PropertyB != null ? PropertyB : defaultValue;
   }
}

Bu durumda, açıkça, PropertyB hiçbir zaman boş olamaz.
yinelemeli

0

Sadece bu yazı karşısında tökezledi.

Bir süre önce Visual Studio Connect'te yeni bir ???operatör eklemeyle ilgili bir öneride bulundum .

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

Bu, çerçeve ekibinden biraz çalışma gerektirir, ancak dili değiştirmeye gerek yoktur, sadece biraz derleyici sihri yapın. Buradaki fikir, derleyicinin bu kodu değiştirmesiydi (sözdizimine izin verilmez atm)

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

bu koda

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()

Boş kontrol için bu şöyle görünebilir

bool isNull = (Order.OrderDetails[0].Product ??? null) == null;

0

Varsayılan bir değeri kabul eden bir yöntem yazdım, işte nasıl kullanılacağı:

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

İşte kod:

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);
    }
}

1
Bu çözüm, diğer yanıtlarda (tekrar tekrar) sağlanmıştır. Tekrar göndermeniz için hiçbir sebep yok .
2014

Varsayılan bir değeri kabul eden herhangi bir şey görmedim.
Akira Yamamoto

Tanımlı bir varsayılan değer kullanan 6 tane daha sayıyorum. Görünüşe göre o kadar da sert görünmemişsin.
2014

0

Mümkün değil. bir hata olan null dereferencing nedeniyle null
ObjectA.PropertyA.PropertyBise başarısız olur ObjectA.

if(ObjectA != null && ObjectA.PropertyA... dolayı kısa devre olmasına eserler, yani ObjectA.PropertyAeğer kontrol asla ObjectAdeğildir null.

Önerdiğiniz ilk yol, niyetle en iyi ve en açık olanıdır. Herhangi bir şey varsa, bu kadar çok boşluğa güvenmek zorunda kalmadan yeniden tasarlamayı deneyebilirsiniz.


-1

Lambda gobbly-gook'u aştığınızda bu yaklaşım oldukça basittir:

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);
        }
    }

Şöyle görünebilecek kullanımla:

ObjectA objectA = null;

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

Assert.IsNull(objectA.GetPropertyOrDefault(prop => prop.ObjectB));

Bir yanıt verdikten 8 yıl sonra neden birinin olumsuz oy kullandığını merak ediyorum (C # 6'nın sıfır birleşiminden yıllar önceydi).
BlackjacketMack

-3
var result = nullableproperty ?? defaultvalue;

??İlk bağımsız değişken ise, (boş birleştirici operatör) araçları nullyerine ikinci bir döner.


2
Bu cevap OP'nin problemini çözmez. Çözümü nasıl uygularsınız? ifadesindeki tüm bölümler (ObjectA, PropertyA ve PropertyB) null olabildiğinde işleci ObjectA.PropertyA.PropertyB'ye?
Artemix

Doğru, sanırım soruyu hiç okumadım. Her neyse, imkansız hiçbir şey yapmayın: P static void Main (string [] args) {a ca = new a (); var default_value = new a () {b = yeni nesne ()}; var değer = (ca ?? varsayılan_değer) .b ?? default_value.b; } a sınıfı {genel nesne b = null; }
Aridane Álamo

(ObjectA ?? DefaultMockedAtNull) .PropertyA! = Null? ObjectA.PropertyA.PropertyB: null
Aridane Álamo
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.