Null Coalescing operatörünü kullanmanın benzersiz yolları [kapalı]


165

C # Null birleştirici işleç kullanmanın standart yolunun varsayılan değerleri ayarlamak olduğunu biliyorum.

string nobody = null;
string somebody = "Bob Saget";
string anybody = "";

anybody = nobody   ?? "Mr. T"; // returns Mr. T
anybody = somebody ?? "Mr. T"; // returns "Bob Saget"

Ama başka ne ??için kullanılabilir? Üçlü operatör olarak, aşağıdakilerden daha özlü ve okunması kolay olmasının yanı sıra yararlı görünmüyor:

nobody = null;
anybody = nobody == null ? "Bob Saget" : nobody; // returns Bob Saget

Böylece boş birleştirici operatör hakkında daha az bilgi sahibi olunca ...

  • ??Başka bir şey için kullandın mı ?

  • ??gerekli, yoksa sadece (çoğu aşina olduğu) üçlü operatörü kullanmalıdır

Yanıtlar:


216

Her şeyden önce, zincirlemek standart üçlüden çok daha kolaydır:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

string anyboby = (parm1 != null) ? parm1 
               : ((localDefault != null) ? localDefault 
               : globalDefault);

Boş olası nesne bir değişken değilse de iyi çalışır:

string anybody = Parameters["Name"] 
              ?? Settings["Name"] 
              ?? GlobalSetting["Name"];

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];

12
Zincirleme, operatör için büyük bir artıdır, bir sürü gereksiz
IF'i

1
Bugün sadece üçlü veya boş birleştirme operatörünü bilmeden önce yazdığım basit bir IF bloğunun yerini aldım. Orijinal IF deyiminin doğru ve yanlış dalları, aynı yöntem olarak adlandırılır ve belirli bir girdi NULL ise bağımsız değişkenlerinden birini farklı bir değerle değiştirir. Boş birleştirme operatörü ile bu bir çağrıdır. Bu iki veya daha fazla ikame gerektiren bir yönteminiz olduğunda bu gerçekten güçlü!
David A. Gray

177

Tembel bir yük tek astar olarak kullandım:

public MyClass LazyProp
{
    get { return lazyField ?? (lazyField = new MyClass()); }
}

Okunabilir? Kendin için karar ver.


6
Hmmm, "neden biri şaşırmış bir IF olarak kullanmak istesin" diye bir karşı örnek buldunuz ... bu aslında benim için çok okunabilir.
Godeke

6
Bir şey eksik olabilirim (çoğunlukla Java kullanıyorum), ama orada bir yarış durumu yok mu?
Justin K

9
@Justin K - Aynı nesnenin LazyProp özelliğine birden çok iş parçacığı erişiyorsa, yalnızca bir yarış koşulu vardır. Her bir örneğin emniyet ipliği gerekiyorsa, bir kilitle kolayca sabitlenebilir. Bu örnekte açıkça gerekli değildir.
Jeffrey L Whitledge

5
@Jeffrey: Açık olsaydı, soruyu sormazdım. :) Bu örneği gördüğümde, hemen tek bir üye düşündüm ve kodlamamın çoğunu çok iş parçacıklı bir ortamda yaptığımdan ... Ama, kodun doğru olduğunu varsayarsak, fazladan bir şey gereksizdir.
Justin K

7
Bir yarış durumuna sahip olmak için Singleton olmak zorunda değildir. LazyProp içeren sınıfın paylaşılan bir örneği ve LazyProp'a erişen birden çok iş parçacığı. Lazy <T> bu tür bir şey yapmanın daha iyi bir yoludur ve varsayılan olarak threadsafe'dir (Lazy <T> 'nin iplik güvenliğini değiştirmeyi seçebilirsiniz).
Niall Connaughton

52

Ben iki "biraz garip" şekilde yararlı buldum:

  • Rutin outyazarken bir parametreye sahip olmak için bir alternatif olarak TryParse(örn. Ayrıştırma başarısız olursa null değerini döndürür)
  • Karşılaştırmalar için "bilmiyorum" temsili olarak

İkincisi biraz daha fazla bilgiye ihtiyaç duyar. Genellikle birden çok öğeyle bir karşılaştırma oluşturduğunuzda, karşılaştırmanın ilk bölümünün (ör. Yaş) kesin bir yanıt verip vermediğini, ardından yalnızca ilk bölüm yardımcı olmadıysa sonraki bölümün (örneğin ad) olup olmadığını görmeniz gerekir. Null birleştirme operatörünün kullanılması, oldukça basit karşılaştırmalar yazabileceğiniz anlamına gelir (sipariş veya eşitlik için olsun). Örneğin, MiscUtil'de birkaç yardımcı sınıf kullanarak :

public int Compare(Person p1, Person p2)
{
    return PartialComparer.Compare(p1.Age, p2.Age)
        ?? PartialComparer.Compare(p1.Name, p2.Name)
        ?? PartialComparer.Compare(p1.Salary, p2.Salary)
        ?? 0;
}

Kuşkusuz şimdi MiscUtil'de ProjectionComparer var, bu tür şeyleri daha da kolaylaştıran bazı uzantılarla birlikte - ama yine de temiz.

Aynı şey, Eşitliklerin uygulanmasının başlangıcında referans eşitliğini (veya geçersizliğini) kontrol etmek için de yapılabilir.


PartialComparer ile yaptıklarınızı seviyorum, ancak değerlendirilen ifade değişkenlerini tutmam gereken durumlarda arıyordum. Lambdalar ve uzantılar konusunda tecrübeli değilim, bu yüzden aşağıdakilerin benzer bir kalıba uyup uymadığını görebiliyor musunuz? stackoverflow.com/questions/1234263/#1241780
maxwellb

33

Bir başka avantaj, üçlü operatörün çifte değerlendirme veya geçici bir değişken gerektirmesidir.

Bunu düşünün, örneğin:

string result = MyMethod() ?? "default value";

üçlü operatörle şunlardan biri ile kalırsınız:

string result = (MyMethod () != null ? MyMethod () : "default value");

MyMethod'u iki kez çağıran veya:

string methodResult = MyMethod ();
string result = (methodResult != null ? methodResult : "default value");

Her iki durumda da, boş birleştirme operatörü daha temiz ve sanırım daha verimlidir.


1
+1. Boş birleştirici operatörü sevmem için büyük bir neden bu. Arama MyMethod()yapmanın herhangi bir yan etkisi olduğunda özellikle kullanışlıdır .
CVn

Eğer MyMethod()dışarıda bir değer dönmek adına bir etkisi yoktur, derleyici bilmez gerçekten vakaların çoğunluğunda burada verimlilik konusunda endişe etmek zorunda kalmamak için, iki kere çağırmaktır.
TinyTimZamboni

Ayrıca, MyMethod()noktalı nesnelerin zincirlenmiş bir dizisi olduğunda işleri daha okunabilir tutar . Örn:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore

@TinyTimZamboni, derleyicinin bu davranışı için bir referansınız var mı?
Kuba Wyrostek

@KubaWyrostek C # derleyicisinin özel çalışmaları hakkında bilgim yok, ancak llvm ile statik derleyici teorisi konusunda deneyimim var. Bir derleyicinin böyle bir çağrıyı optimize etmek için yapabileceği birkaç yaklaşım vardır. Global Değer Numaralandırma , Saf bir işlev olduğu MyMethodvarsayılarak, bu bağlamda iki çağrının aynı olduğunu fark edecektir MyMethod. Başka bir seçenek Otomatik Bellek veya sadece önbellekte işlevi kapatmaktır. Öte yandan: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni

23

Dikkate alınması gereken başka bir şey, birleştirme operatörünün üçlü gibi bir özelliğin get yöntemini iki kez çağırmamasıdır.

Üçlü kullanmamanız gereken senaryolar var, örneğin:

public class A
{
    var count = 0;
    private int? _prop = null;
    public int? Prop
    {
        get 
        {
            ++count;
            return _prop
        }
        set
        {
            _prop = value;
        }
    }
}

Eğer kullanırsan:

var a = new A();
var b = a.Prop == null ? 0 : a.Prop;

alıcı iki kez çağrılır ve countdeğişken 2'ye eşit olur ve kullanırsanız:

var b = a.Prop ?? 0

countolması gerektiği gibi değişken 1'e eşittir olacaktır.


4
Bu daha fazla oyu hak ediyor. Ben cook defalarca okudum ??olduğunu eşdeğer etmek ?:.
Kuba Wyrostek

1
Bir alıcıya iki kez çağrı yapılmasına ilişkin geçerli nokta. Ancak bu örnek, aslında nesnede değişiklikler yapmak için yanıltıcı olarak adlandırılan alıcıya sahip kötü bir tasarım bilmecesini düşünürdüm.
Linas

15

??Operatöre bulduğum en büyük avantaj, nullable değer türlerini nullable olmayan türlere kolayca dönüştürebilmenizdir:

int? test = null;
var result = test ?? 0; // result is int, not int?

Bunu sık sık Linq sorgularında kullanıyorum:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?

Burada biraz geç kalabilirim, ama eğer ikinci örnek atar kvp == null. Ve aslında normalde kullandığım Nullable<T>bir GetValueOrDefaultyöntem var.
CompuChip

6
KeyValuePair .NET çerçevesindeki bir değer türüdür, bu nedenle özelliklerinden herhangi birine erişmek hiçbir zaman boş bir başvuru istisnası atmaz. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan

9

Kullandım ?? IDataErrorInfo uygulamamda:

public string Error
{
    get
    {
        return this["Name"] ?? this["Address"] ?? this["Phone"];
    }
}

public string this[string columnName]
{
    get { ... }
}

Herhangi bir bireysel özellik "hata" durumunda ise, bu hatayı alıyorum, aksi takdirde null olsun. Gerçekten iyi çalışıyor.


İlginç. "Bunu" mülk olarak kullanıyorsunuz. Bunu hiç yapmadım.
Armstrongest

Evet, IDataErrorInfo'un nasıl çalıştığının bir parçası. Genellikle bu sözdizimi yalnızca toplama sınıflarında yararlıdır.
Matt Hamilton

4
Sen hata mesajlarının depolanması vardır this["Name"], this["Address"]vs ??
Andrew

7

İsteğe bağlı bir parametrenin ayarlanmadığı durumu işlemeyi biraz daha temiz hale getirmek için null birleştirme operatörünü kullanabilirsiniz:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...

Bu satırın yazılabilmesi harika olmaz mıydı arg ?= Arg.Default?
Jesse de Wit

6

Belirli özellikleri tembel yüklemek için null birleştirme operatörünü kullanmayı seviyorum.

Sadece benim açımdan örnek vermek için çok basit bir örnek.

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}

Yeniden şekillendirici aslında bunu "geleneksel" tembel yük için bir refaktör olarak önerecektir.
arichards

5

Dır-dir ?? ya da sadece üçlü operatörü (en çok tanıdık olan)

Aslında, benim deneyimim, çok az insanın üçlü operatörle (veya daha doğru bir şekilde, koşullu operatörle) aşina ?:olması ||, ikili veya ikili veya ikili olarak aynı anlamda "üçlü" olmasıdır +; yalnızca birçok dilde üçlü operatör), dolayısıyla en azından bu sınırlı örnekte ifadeniz burada başarısız olur.

Ayrıca, daha önce de belirtildiği gibi, boş birleştirme operatörünün çok faydalı olduğu önemli bir durum vardır ve bu, değerlendirilecek ifadenin herhangi bir yan etkisi olduğu zaman ortaya çıkar. Bu durumda, olamaz uygulamanın fiili mantığının değiştirilmesi geçici bir değişken, veya (b) tanıtan ya (a) olmadan koşullu operatörü kullanabilirsiniz. (b) hiçbir koşulda açıkça uygun değildir ve kişisel bir tercih olsa da, kısa ömürlü bile olsa, bildirim kapsamını çok fazla yabancı ile karıştırmaktan hoşlanmam, bu nedenle (a) özel senaryo.

Tabii ki, sonuç üzerinde birden fazla kontrol yapmanız gerekiyorsa, koşullu operatör veya bir dizi ifblok muhtemelen iş için bir araçtır. Ama basit olarak "bu null ise, bunu kullanın, aksi takdirde kullanın", null birleştirme operatörü ??mükemmeldir.


Benden çok geç bir yorum - ama birisinin üçlü bir operatörün üç argümanı olan (şimdi C # 'da birden fazla olan) bir operatör olduğunu görmesinden memnunum.
Steve Kidd

5

Son zamanlarda çok şey yaptığım şey, yedeklemeler için null birleştirme kullanmaktır as. Örneğin:

object boxed = 4;
int i = (boxed as int?) ?? 99;

Console.WriteLine(i); // Prints 4

Her ?.birinin başarısız olabileceği uzun zincirleri yedeklemek için de yararlıdır

int result = MyObj?.Prop?.Foo?.Val ?? 4;
string other = (MyObj?.Prop?.Foo?.Name as string)?.ToLower() ?? "not there";

4

Tek sorun, boş değer birleştirme işlecinin boş dizeleri algılamamasıdır.


yani

string result1 = string.empty ?? "dead code!";

string result2 = null ?? "coalesced!";

ÇIKTI:

result1 = ""

result2 = coalesced!

Şu anda? operatör bu sorunu çözmek için. Bunu Çerçeve içine yerleştirmek faydalı olacaktır.

Düşünceler?


Bunu Uzantı yöntemleri ile yapabilirsiniz, ancak katılıyorum, koda hoş bir ek olacağını ve bir web bağlamında çok yararlı olacağını kabul ediyorum.
Armstrongest

Evet, bu sık karşılaşılan bir senaryodur ... Özel bir yöntem bile var String.IsNullOrEmpty (string) ...
Max Galkin

12
"null-coalesce operatörü boş dizeleri algılamıyor." Eh öyle boş -coalescing operatörü değil nullOrEmpty -coalescing operatörü. Ve kişisel olarak, ikisi arasında ayrım yapan dilde null ve boş değerlerin karıştırılmasını hor görüyorum, bu da oldukça can sıkıcı olmayan şeylerle arayüz oluşturuyor. Ve ben biraz takıntılı-kompulsifim, bu yüzden nedenini (neden [?)? SQL'de olduğu gibi) anlayabilsem bile, diller / uygulamalar ikisi arasında ayrım yapmadığında beni rahatsız ediyor.
JAB

3
??aşırı yüklenemez: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - Bu, aşırı yüklenebilir olmak için harika bir seçenek olacaktır. Bir kombinasyon kullanın: s1.Nullify() ?? s2.Nullify()nerede dizesi boş olduğunda durumlarda string Nullify(this s)döndürür null.
Kit

Tek sorun? Sadece kendimi isteyen buldum ?? = ve bunu yapmak için bir yol olup olmadığını görünce bu konu bulundu. (Durum: İlk geçiş istisna durumları yükledi, şimdi geri dönüp önceden yüklenmemiş bir şeye varsayılan değerleri yüklemek istiyorum.)
Loren Pechtel

3

Dır-dir ?? ya da sadece üçlü operatörü (en çok tanıdık olan)

Niyetinizi en iyi ifade eden şeyi kullanmalısınız. Orada bu yana ise boş kaynaşabilecek operatörü kullanabilirsiniz .

Öte yandan, bu kadar uzmanlaşmış olduğu için, başka kullanımları olduğunu düşünmüyorum. ||Diğer diller gibi operatörün de aşırı yüklenmesini tercih ederdim . Bu, dil tasarımında daha cimri olacaktır. Ama iyi…


3

Güzel! Beni sıfır birleştirici operatör hakkında bilmeyen biri olarak say - bu oldukça şık şeyler.

Üçlü operatörden okumayı çok daha kolay buluyorum.

Aklıma gelen ilk yer, tüm varsayılan parametrelerimi tek bir yerde tutmaktır.

public void someMethod( object parm2, ArrayList parm3 )
{ 
  someMethod( null, parm2, parm3 );
}
public void someMethod( string parm1, ArrayList parm3 )
{
  someMethod( parm1, null, parm3 );
}
public void someMethod( string parm1, object parm2, )
{
  someMethod( parm1, parm2, null );
}
public void someMethod( string parm1 )
{
  someMethod( parm1, null, null );
}
public void someMethod( object parm2 )
{
  someMethod( null, parm2, null );
}
public void someMethod( ArrayList parm3 )
{
  someMethod( null, null, parm3 );
}
public void someMethod( string parm1, object parm2, ArrayList parm3 )
{
  // Set your default parameters here rather than scattered through the above function overloads
  parm1 = parm1 ?? "Default User Name";
  parm2 = parm2 ?? GetCurrentUserObj();
  parm3 = parm3 ?? DefaultCustomerList;

  // Do the rest of the stuff here
}

2

Biraz garip bir kullanım durumu, ancak bir IDisposablenesnenin potansiyel olarak bir arg olarak geçtiği (ve dolayısıyla ebeveyn tarafından atılan) bir yöntemim vardı , ancak aynı zamanda boş da olabilir (ve böylece yerel yöntemde oluşturulmalı ve atılmalıdır)

Kullanmak için, kod ya

Channel channel;
Authentication authentication;

if (entities == null)
{
    using (entities = Entities.GetEntities())
    {
        channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
        [...]
    }
}
else
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

Ama boş bir birleşim çok daha temiz olur

using (entities ?? Entities.GetEntities())
{
    channel = entities.GetChannelById(googleShoppingChannelCredential.ChannelId);
    [...]
}

0

Ben böyle kullandım:

for (int i = 0; i < result.Count; i++)
            {
                object[] atom = result[i];

                atom[3] = atom[3] ?? 0;
                atom[4] = atom[4] != null ? "Test" : string.Empty;
                atom[5] = atom[5] ?? "";
                atom[6] = atom[6] ?? "";
                atom[7] = atom[7] ?? "";
                atom[8] = atom[8] ?? "";
                atom[9] = atom[9] ?? "";
                atom[10] = atom[10] ?? "";
                atom[12] = atom[12] ?? false; 
            }
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.