Cara unik untuk menggunakan operator Null Coalescing [ditutup]

164

Saya tahu cara standar menggunakan operator penggabungan Null di C # adalah dengan menetapkan nilai default.

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

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

Tapi untuk apa lagi bisa ??digunakan? Tampaknya tidak berguna seperti operator ternary, selain lebih ringkas dan lebih mudah dibaca daripada:

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

Jadi mengingat bahwa lebih sedikit yang tahu tentang operator penggabungan nol ...

  • Apakah Anda pernah menggunakan yang ??lain?

  • Apakah ??diperlukan, atau jika Anda hanya menggunakan operator ternary (yang paling akrab dengan)

Terkuat
sumber

Jawaban:

216

Pertama-tama, ini jauh lebih mudah untuk dirantai daripada terner standar:

string anybody = parm1 ?? localDefault ?? globalDefault;

vs.

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

Ini juga berfungsi dengan baik jika objek null-mungkin bukan variabel:

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

vs.

string anybody = (Parameters["Name"] != null ? Parameters["Name"] 
                 : (Settings["Name"] != null) ? Settings["Name"]
                 :  GlobalSetting["Name"];
James Curran
sumber
12
Chaining adalah nilai tambah besar bagi operator, menghilangkan sekelompok IF berlebihan
chakrit
1
Saya hanya menggunakannya hari ini untuk mengganti blok IF sederhana yang saya tulis sebelum saya tahu tentang operator penggabungan ternary atau null. Cabang benar dan salah dari pernyataan IF asli disebut metode yang sama, mengganti salah satu argumennya dengan nilai yang berbeda jika input tertentu NULL. Dengan operator penggabungan nol, ini adalah satu panggilan. Ini sangat kuat ketika Anda memiliki metode yang membutuhkan dua atau lebih penggantian seperti itu!
David A. Gray
177

Saya telah menggunakannya sebagai pemuat satu baris yang malas:

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

Dibaca? Putuskan sendiri.

Cristian Libardo
sumber
6
Hmmm, Anda menemukan contoh tandingan untuk "mengapa seseorang ingin menggunakannya sebagai JIKA kabur" ... yang sebenarnya sangat mudah dibaca oleh saya.
Godeke
6
Saya mungkin kehilangan sesuatu (kebanyakan saya menggunakan Java), tetapi tidakkah ada kondisi lomba di sana?
Justin K
9
@Justin K - Hanya ada kondisi lomba jika beberapa utas mengakses properti LazyProp dari objek yang sama. Ini mudah diperbaiki dengan kunci, jika keselamatan thread dari setiap instance diperlukan. Jelas dalam contoh ini, itu tidak diperlukan.
Jeffrey L Whitledge
5
@ Jeffrey: Jika sudah jelas, saya tidak akan bertanya. :) Ketika saya melihat contoh itu, saya langsung teringat anggota singleton, dan karena saya kebetulan melakukan sebagian besar pengkodean saya di lingkungan multithreaded ... Tapi, ya, kalau kita anggap kodenya benar, semua tambahan tidak diperlukan.
Justin K
7
Tidak harus menjadi Singleton untuk memiliki kondisi balapan. Hanya instance bersama dari kelas yang berisi LazyProp, dan beberapa utas yang mengakses LazyProp. Lazy <T> adalah cara yang lebih baik untuk melakukan hal semacam ini, dan secara default threadsafe (Anda dapat memilih untuk mengubah keamanan thread dari Lazy <T>).
Niall Connaughton
52

Saya menemukan ini berguna dalam dua cara "sedikit aneh":

  • Sebagai alternatif untuk memiliki outparameter saat menulis TryParserutinitas (yaitu mengembalikan nilai nol jika parsing gagal)
  • Sebagai representasi "tidak tahu" untuk perbandingan

Yang terakhir membutuhkan sedikit lebih banyak informasi. Biasanya ketika Anda membuat perbandingan dengan beberapa elemen, Anda perlu melihat apakah bagian pertama dari perbandingan (misalnya usia) memberikan jawaban yang pasti, kemudian bagian berikutnya (misalnya nama) hanya jika bagian pertama tidak membantu. Menggunakan operator penggabungan nol berarti Anda dapat menulis perbandingan yang cukup sederhana (baik untuk pemesanan atau kesetaraan). Misalnya, menggunakan beberapa kelas pembantu di MiscUtil :

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

Memang sekarang saya memiliki ProjectionComparer di MiscUtil, bersama dengan beberapa ekstensi, yang membuat hal semacam ini lebih mudah - tetapi masih rapi.

Hal yang sama dapat dilakukan untuk memeriksa kesetaraan referensi (atau nullity) pada awal penerapan Equals.

Jon Skeet
sumber
Saya suka apa yang Anda lakukan dengan PartialComparer, tetapi sedang mencari kasus di mana saya perlu menjaga variabel ekspresi yang dievaluasi. Saya tidak berpengalaman dalam lambdas dan ekstensi, jadi dapatkah Anda melihat apakah mengikuti pola yang sama (yaitu apakah itu berfungsi)? stackoverflow.com/questions/1234263/#1241780
maxwellb
33

Keuntungan lain adalah bahwa operator ternary memerlukan evaluasi ganda atau variabel sementara.

Pertimbangkan ini, misalnya:

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

sementara dengan operator ternary Anda dibiarkan:

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

yang memanggil MyMethod dua kali, atau:

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

Either way, operator penggabungan nol lebih bersih dan, saya kira, lebih efisien.


sumber
1
+1. Ini adalah salah satu alasan utama mengapa saya suka operator penggabungan nol. Ini sangat berguna ketika menelepon MyMethod()memiliki efek samping apa pun.
CVn
Jika MyMethod()tidak memiliki efek di luar pengembalian nilai, kompiler tahu untuk tidak menyebutnya dua kali, jadi Anda benar-benar tidak perlu khawatir tentang efisiensi di sini di sebagian besar kasus.
TinyTimZamboni
Hal ini juga membuat hal-hal IMHO lebih mudah dibaca ketika MyMethod()urutan rantai objek bertitik. Contoh:myObject.getThing().getSecondThing().getThirdThing()
xdhmoore
@TinyTimZamboni, apakah Anda memiliki referensi untuk perilaku kompiler ini?
Kuba Wyrostek
@KubaWyrostek Saya tidak memiliki pengetahuan tentang cara kerja spesifik dari kompiler C #, tetapi saya memiliki beberapa pengalaman dengan teori kompiler statis dengan llvm. Ada sejumlah pendekatan yang dapat dilakukan oleh kompiler untuk mengoptimalkan panggilan seperti ini. Penomoran Nilai Global akan melihat bahwa kedua panggilan ke MyMethodidentik dalam konteks ini, dengan asumsi itu MyMethodadalah fungsi Murni. Pilihan lain adalah Memoisasi Otomatis atau fungsi tutup cache. Di sisi lain: en.wikipedia.org/wiki/Global_value_numbering
TinyTimZamboni
23

Satu hal yang perlu dipertimbangkan adalah bahwa operator gabungan tidak memanggil metode get properti dua kali, seperti yang dilakukan ternary.

Jadi ada skenario di mana Anda tidak boleh menggunakan ternary, misalnya:

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

Jika Anda menggunakan:

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

pengambil akan dipanggil dua kali dan countvariabel akan sama dengan 2, dan jika Anda menggunakan:

var b = a.Prop ?? 0

yang countvariabel akan sama dengan 1, sebagaimana mestinya.

Fabio Lima
sumber
4
Ini layak mendapatkan lebih banyak suara positif. Saya sudah membaca sooo berkali-kali bahwa ??adalah setara dengan ?:.
Kuba Wyrostek
1
Poin valid tentang seorang pengambil dipanggil dua kali. Tapi contoh ini saya akan mempertimbangkan derai desain yang buruk untuk memiliki pengambil bernama misleadingly untuk benar-benar membuat perubahan pada objek.
Linas
15

Keuntungan terbesar yang saya temukan kepada ??operator adalah Anda dapat dengan mudah mengkonversi tipe nilai yang dapat dibatalkan menjadi tipe yang tidak dapat dibatalkan:

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

Saya sering menggunakan ini dalam permintaan Linq:

Dictionary<int, int?> PurchaseQuantities;
// PurchaseQuantities populated via ASP .NET MVC form.
var totalPurchased = PurchaseQuantities.Sum(kvp => kvp.Value ?? 0);
// totalPurchased is int, not int?
Ryan
sumber
Saya mungkin agak terlambat di sini, tetapi contoh kedua itu akan melempar jika kvp == null. Dan sebenarnya Nullable<T>memiliki GetValueOrDefaultmetode yang biasa saya gunakan.
CompuChip
6
KeyValuePair adalah tipe nilai dalam kerangka .NET, jadi mengakses salah satu propertinya tidak akan pernah membuang pengecualian referensi nol. msdn.microsoft.com/en-us/library/5tbh8a42(v=vs.110).aspx
Ryan
9

Saya sudah menggunakan ?? dalam implementasi IDataErrorInfo saya:

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

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

Jika ada properti individu dalam keadaan "kesalahan" saya mendapatkan kesalahan itu, kalau tidak saya akan null. Bekerja dengan sangat baik.

Matt Hamilton
sumber
Menarik. Anda menggunakan "ini" sebagai properti. Saya belum pernah melakukannya.
Armstrongest
Ya, itu bagian dari cara kerja IDataErrorInfo. Secara umum sintaks hanya berguna pada kelas koleksi.
Matt Hamilton
4
Anda menyimpan pesan error di this["Name"], this["Address"], dll ??
Andrew
7

Anda dapat menggunakan operator penggabungan nol untuk membuatnya sedikit lebih bersih untuk menangani case di mana parameter opsional tidak diatur:

public void Method(Arg arg = null)
{
    arg = arg ?? Arg.Default;
    ...
Niall Connaughton
sumber
Bukankah lebih bagus jika baris ini bisa ditulis arg ?= Arg.Default?
Jesse de Wit
6

Saya suka menggunakan operator penggabungan nol untuk malas memuat properti tertentu.

Contoh yang sangat sederhana (dan dibuat-buat) hanya untuk mengilustrasikan poin saya:

public class StackOverflow
{
    private IEnumerable<string> _definitions;
    public IEnumerable<string> Definitions
    {
        get
        {
            return _definitions ?? (
                _definitions = new List<string>
                {
                    "definition 1",
                    "definition 2",
                    "definition 3"
                }
            );
        }
    } 
}
mlnyc
sumber
Resharper sebenarnya akan menyarankan ini sebagai refactor untuk muatan malas "tradisional".
arichards
5

Adalah ?? diperlukan, atau sebaiknya Anda hanya menggunakan operator ternary (yang paling akrab)

Sebenarnya, pengalaman saya adalah bahwa terlalu sedikit orang yang akrab dengan operator ternary (atau lebih tepatnya, operator kondisional ; ?:adalah "ternary" dalam arti yang sama yaitu ||biner atau +unary atau binary; itu memang kebetulan hanya operator ternary dalam banyak bahasa), jadi setidaknya dalam sampel terbatas itu, pernyataan Anda gagal di sana.

Juga, seperti yang disebutkan sebelumnya, ada satu situasi besar ketika operator penggabungan nol sangat berguna, dan saat itulah ekspresi yang akan dievaluasi memiliki efek samping sama sekali. Dalam hal itu, Anda tidak dapat menggunakan operator kondisional tanpa (a) memperkenalkan variabel sementara, atau (b) mengubah logika aplikasi yang sebenarnya. (B) jelas tidak sesuai dalam keadaan apa pun, dan sementara itu adalah preferensi pribadi, saya tidak suka mengacaukan ruang lingkup deklarasi dengan banyak variabel asing, bahkan jika berumur pendek, sehingga (a) terlalu dalam bahwa skenario tertentu.

Tentu saja, jika Anda perlu melakukan beberapa pemeriksaan pada hasilnya, operator bersyarat, atau serangkaian ifblok, mungkin merupakan alat untuk pekerjaan itu. Tetapi untuk sederhana "jika ini nol, gunakan itu, kalau tidak gunakan itu", operator penggabungan nol ??sempurna.

sebuah CVn
sumber
Sangat terlambat komentar dari saya - tetapi senang melihat seseorang menutupi bahwa operator ternary adalah operator dengan tiga argumen (yang sekarang ada lebih dari satu di C #).
Steve Kidd
5

Satu hal yang banyak saya lakukan belakangan ini adalah menggunakan null coalescing untuk backup as. Sebagai contoh:

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

Console.WriteLine(i); // Prints 4

Ini juga berguna untuk membackup rantai panjang ?.yang masing-masing bisa gagal

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

Satu-satunya masalah adalah operator null-coalesce tidak mendeteksi string kosong.


yaitu

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

string result2 = null ?? "coalesced!";

KELUARAN:

result1 = ""

result2 = coalesced!

Saya sedang mencari untuk mengesampingkan ?? operator untuk mengatasi ini. Ini tentu akan berguna untuk memiliki ini dibangun ke dalam Kerangka.

Pikiran?

Kevin Panko
sumber
Anda dapat melakukan ini dengan metode Extension, tetapi saya setuju, itu akan menjadi tambahan yang bagus untuk kode dan sangat berguna dalam konteks web.
Armstrongest
Ya, ini adalah skenario yang sering terjadi ... bahkan ada metode khusus String.IsNullOrEmpty (string) ...
Max Galkin
12
"Operator null-coalesce tidak mendeteksi string kosong." Yah itu operator null -coalescing, bukan -coalescing nullOrEmpty operator. Dan secara pribadi, saya benci mencampurkan nilai nol dan kosong dalam bahasa yang membedakan keduanya, yang membuat berinteraksi dengan hal-hal yang tidak terlalu mengganggu. Dan saya agak obsesif-kompulsif, jadi itu mengganggu saya ketika bahasa / implementasi tidak membedakan antara keduanya, bahkan jika saya mengerti alasan mengapa (seperti dalam [kebanyakan implementasi?] SQL).
JAB
3
??tidak dapat kelebihan beban: msdn.microsoft.com/en-us/library/8edha89s(v=vs.100).aspx - itu akan menjadi hal yang bagus untuk memiliki kelebihan muatan. Saya menggunakan kombinasi: di s1.Nullify() ?? s2.Nullify()mana string Nullify(this s)mengembalikan nulldalam kasus ketika string kosong.
Kit
Satu-satunya masalah? Saya baru saja mendapati diri saya ingin ?? = dan menemukan utas ini sambil melihat apakah ada cara untuk melakukannya. (Situasi: Lulus pertama memuat kasus pengecualian, sekarang saya ingin kembali dan memuat nilai default ke apa pun yang belum dimuat.)
Loren Pechtel
3

Adalah ?? diperlukan, atau sebaiknya Anda hanya menggunakan operator ternary (yang paling akrab)

Anda harus menggunakan apa yang terbaik mengekspresikan niat Anda. Karena ada adalah operator menyatu null, menggunakannya .

Di sisi lain, karena sangat khusus, saya tidak berpikir itu memiliki kegunaan lain. Saya lebih suka ||operator yang kelebihan beban , seperti bahasa lainnya. Ini akan lebih pelit dalam desain bahasa. Tapi yah ...

Konrad Rudolph
sumber
3

Keren! Anggap saya sebagai seseorang yang tidak tahu tentang operator penggabungan nol - itu hal yang sangat bagus.

Saya merasa jauh lebih mudah dibaca daripada operator ternary.

Tempat pertama yang terlintas dalam pikiran di mana saya mungkin menggunakannya adalah untuk menyimpan semua parameter default saya di satu tempat.

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
}
HanClinto
sumber
2

Sedikit kasus penggunaan yang aneh, tapi saya punya metode di mana IDisposableobjek berpotensi dilewatkan sebagai argumen (dan karena itu dibuang oleh orang tua), tetapi juga bisa menjadi nol (dan karenanya harus dibuat dan dibuang dalam metode lokal)

Untuk menggunakannya, kodenya terlihat seperti

Channel channel;
Authentication authentication;

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

Tetapi dengan nol bersatu menjadi jauh lebih rapi

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

Saya telah menggunakan seperti ini:

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; 
            }
Muhammad Awais
sumber