Apa cara yang tepat untuk memeriksa nilai null?

122

Saya suka operator penggabungan nol karena membuatnya mudah untuk menetapkan nilai default untuk tipe nullable.

 int y = x ?? -1;

Itu bagus, kecuali jika saya perlu melakukan sesuatu yang sederhana x. Misalnya, jika saya ingin memeriksa Session, biasanya saya harus menulis sesuatu yang lebih bertele-tele.

Saya berharap saya bisa melakukan ini:

string y = Session["key"].ToString() ?? "none";

Tetapi Anda tidak bisa karena .ToString()dipanggil sebelum pemeriksaan null sehingga gagal jika Session["key"]null. Saya akhirnya melakukan ini:

string y = Session["key"] == null ? "none" : Session["key"].ToString();

Ini berfungsi dan lebih baik, menurut saya, daripada alternatif tiga baris:

string y = "none";
if (Session["key"] != null)
    y = Session["key"].ToString();

Meskipun itu berhasil, saya masih penasaran apakah ada cara yang lebih baik. Sepertinya tidak peduli apa yang selalu saya rujuk Session["key"]dua kali; sekali untuk cek, dan sekali lagi untuk tugas. Ada ide?

Chev
sumber
20
Ini adalah saat saya berharap C # memiliki "operator navigasi yang aman" ( .?) seperti yang dimiliki Groovy .
Cameron
2
@ Kameron: Ini adalah saat saya berharap C # dapat memperlakukan tipe nullable (termasuk tipe referensi) sebagai monad, jadi Anda tidak memerlukan "operator navigasi yang aman".
Jon Purdy
3
Penemu referensi nol menyebutnya "kesalahan miliaran dolar" dan saya cenderung setuju. Lihat infoq.com/presentations/…
Jamie Ide
Kesalahan sebenarnya adalah pencampuran yang tidak aman (tidak dipaksakan oleh bahasa) dari tipe nullable dan non-nullabel.
MSalters
@JamieIde Terima kasih untuk tautan yang sangat menarik. :)
BobRodes

Jawaban:

182

Bagaimana dengan

string y = (Session["key"] ?? "none").ToString();
Beruang hitam
sumber
79
Kekuatannya kuat dengan yang satu ini.
Chev
2
@Matthew: Tidak karena nilai Sesi berjenis Objek
BlackBear
1
@BlackBear tetapi nilai yang dikembalikan kemungkinan besar adalah string, jadi pemerannya valid
Firo
Ini adalah jawaban paling langsung untuk pertanyaan saya, jadi saya menandai jawabannya, tetapi metode ekstensi Jon Skeet adalah cara .ToStringOrDefault()yang saya sukai untuk melakukannya. Namun, saya menggunakan jawaban ini dalam metode ekstensi Jon;)
Chev
10
Saya tidak suka ini karena jika Anda memiliki jenis objek lain yang dimasukkan dalam sesi daripada yang Anda harapkan, Anda mungkin menyembunyikan beberapa bug halus dalam program Anda. Saya lebih suka menggunakan gips yang aman karena menurut saya kemungkinan akan memunculkan kesalahan lebih cepat. Ini juga menghindari pemanggilan ToString () pada objek string.
tvanfosson
130

Jika Anda sering melakukan ini secara khusus,ToString() Anda dapat menulis metode ekstensi:

public static string NullPreservingToString(this object input)
{
    return input == null ? null : input.ToString();
}

...

string y = Session["key"].NullPreservingToString() ?? "none";

Atau metode yang mengambil default, tentu saja:

public static string ToStringOrDefault(this object input, string defaultValue)
{
    return input == null ? defaultValue : input.ToString();
}

...

string y = Session["key"].ToStringOrDefault("none");
Jon Skeet
sumber
16
.ToStringOrDefault()sederhana dan elegan. Solusi yang bagus.
Chev
7
Saya sama sekali tidak setuju dengan ini. Metode ekstensi pada objectadalah kutukan dan membuang basis kode, dan metode ekstensi yang beroperasi tanpa kesalahan pada thisnilai nol adalah kejahatan murni.
Nick Larsen
10
@NickLarsen: Semuanya secukupnya, kataku. Metode ekstensi yang bekerja dengan null bisa sangat berguna, IMO - selama mereka jelas tentang apa yang mereka lakukan.
Jon Skeet
3
@ one.beat.consumer: Yup. Jika itu hanya pemformatan (atau kesalahan ketik), itu akan menjadi satu hal, tetapi mengubah pilihan nama metode penulis adalah di luar apa yang biasanya diedit dengan tepat, IMO.
Jon Skeet
6
@ one.beat.consumer: Saat mengoreksi tata bahasa dan kesalahan ketik, tidak apa-apa - tetapi mengubah nama yang sengaja dipilih oleh seseorang (siapa pun, bukan hanya saya) terasa berbeda bagi saya. Pada titik itu, saya akan menyarankannya dalam komentar.
Jon Skeet
21

Anda juga bisa menggunakan as, yang menghasilkan nulljika konversi gagal:

Session["key"] as string ?? "none"

Ini akan kembali "none"bahkan jika seseorang boneka sebuah intdi Session["key"].

Andomar
sumber
1
Ini hanya berfungsi ketika Anda tidak membutuhkannya ToString()sejak awal.
Abel
1
Saya terkejut belum ada yang meremehkan jawaban ini. Ini secara semantik sangat berbeda dari apa yang ingin dilakukan OP.
Timwi
@ Timwi: OP digunakan ToString()untuk mengonversi objek yang berisi string menjadi string. Anda dapat melakukan hal yang sama dengan obj as stringatau (string)obj. Ini adalah situasi yang cukup umum di ASP.NET.
Andomar
5
@Andomar: Tidak, OP memanggil ToString()objek (yaitu, Session["key"]) yang tipenya tidak dia sebutkan. Ini bisa berupa objek apa saja, tidak harus berupa string.
Timwi
13

Jika akan selalu menjadi string, Anda dapat mentransmisikan:

string y = (string)Session["key"] ?? "none";

Ini memiliki keuntungan dari mengeluh daripada menyembunyikan kesalahan jika seseorang memasukkan intatau sesuatu Session["key"]. ;)

Ry-
sumber
10

Semua solusi yang disarankan bagus, dan jawab pertanyaannya; jadi ini hanya untuk memperpanjangnya sedikit. Saat ini mayoritas jawaban hanya berurusan dengan validasi null dan tipe string. Anda dapat memperluas StateBagobjek untuk menyertakan GetValueOrDefaultmetode umum , mirip dengan jawaban yang diposting oleh Jon Skeet.

Metode ekstensi generik sederhana yang menerima string sebagai kunci, lalu ketik memeriksa objek sesi. Jika objek null atau bukan jenis yang sama, default dikembalikan, jika nilai sesi dikembalikan sangat diketik.

Sesuatu seperti ini

/// <summary>
/// Gets a value from the current session, if the type is correct and present
/// </summary>
/// <param name="key">The session key</param>
/// <param name="defaultValue">The default value</param>
/// <returns>Returns a strongly typed session object, or default value</returns>
public static T GetValueOrDefault<T>(this HttpSessionState source, string key, T defaultValue)
{
    // check if the session object exists, and is of the correct type
    object value = source[key]
    if (value == null || !(value is T))
    {
        return defaultValue;
    }

    // return the session object
    return (T)value;
}
Richard
sumber
1
Dapatkah Anda menyertakan contoh penggunaan untuk metode ekstensi ini? Apakah StateBag tidak berurusan dengan status tampilan dan bukan sesi? Saya menggunakan ASP.NET MVC 3 jadi saya tidak benar-benar memiliki akses sederhana untuk melihat status. Saya pikir Anda ingin memperpanjang HttpSessionState.
Chev
jawaban ini membutuhkan pengambilan nilai 3x dan 2 cast jika berhasil. (Saya tahu ini kamus, tetapi para pemula dapat menggunakan praktik serupa pada metode mahal.)
Jake Berger
3
T value = source[key] as T; return value ?? defaultValue;
Jake Berger
1
@jberger Mentransmisikan ke nilai menggunakan "as" tidak dapat diakses karena tidak ada batasan kelas pada jenis umum karena Anda mungkin ingin mengembalikan nilai seperti bool. @AlexFord Maaf, Anda ingin memperpanjang HttpSessionStatesesi ini. :)
Richard
memang. seperti yang dicatat Richard, membutuhkan batasan. (... dan metode lain jika Anda ingin menggunakan tipe nilai)
Jake Berger
7

Kami menggunakan metode yang disebut NullOr.

Pemakaian

// Call ToString() if it’s not null, otherwise return null
var str = myObj.NullOr(obj => obj.ToString());

// Supply default value for when it’s null
var str = myObj.NullOr(obj => obj.ToString()) ?? "none";

// Works with nullable return values, too —
// this is properly typed as “int?” (nullable int)
// even if “Count” is just int
var count = myCollection.NullOr(coll => coll.Count);

// Works with nullable input types, too
int? unsure = 47;
var sure = unsure.NullOr(i => i.ToString());

Sumber

/// <summary>Provides a function delegate that accepts only value types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>.</remarks>
public delegate TResult FuncStruct<in TInput, TResult>(TInput input) where TResult : struct;

/// <summary>Provides a function delegate that accepts only reference types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>.</remarks>
public delegate TResult FuncClass<in TInput, TResult>(TInput input) where TResult : class;

/// <summary>Provides extension methods that apply to all types.</summary>
public static class ObjectExtensions
{
    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult NullOr<TInput, TResult>(this TInput input, FuncClass<TInput, TResult> lambda) where TResult : class
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, Func<TInput, TResult?> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, FuncStruct<TInput, TResult> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input).Nullable();
    }
}
Timwi
sumber
Ya, ini adalah jawaban yang lebih umum untuk masalah yang dimaksudkan - Anda mengalahkan saya untuk itu - dan kandidat untuk navigasi yang aman (jika Anda tidak keberatan lambda-s untuk hal-hal sederhana) - tetapi masih agak rumit untuk menulis, baik :). Secara pribadi saya selalu memilih? : sebagai gantinya (jika tidak mahal, jika kemudian diatur ulang) ...
NSGaga-kebanyakan-tidak aktif
... Dan 'Penamaan' adalah masalah sebenarnya dengan yang satu ini - tampaknya tidak ada yang benar-benar menggambarkan dengan benar (atau 'menambahkan' terlalu banyak), atau panjang - NullOr bagus tetapi terlalu banyak penekanan pada IMO 'null' (ditambah Anda have ?? still) - 'Property', atau 'Safe' adalah yang saya gunakan. value.Dot (o => o.property) ?? @default mungkin?
NSGaga-kebanyakan-tidak aktif
@NSGaga: Kami bolak-balik menyebutkan nama untuk beberapa waktu. Kami memang mempertimbangkannya Dottetapi menganggapnya terlalu tidak deskriptif. Kami memutuskan NullOrsebagai pertukaran yang baik antara penjelasan diri dan singkatnya. Jika Anda sama sekali tidak peduli dengan penamaannya, Anda selalu dapat menyebutnya _. Jika Anda merasa lambda terlalu rumit untuk ditulis, Anda dapat menggunakan potongan untuk ini, tetapi secara pribadi saya merasa cukup mudah. Adapun ? :, Anda tidak dapat menggunakannya dengan ekspresi yang lebih kompleks, Anda harus memindahkannya ke lokal baru; NullOrmemungkinkan Anda untuk menghindari itu.
Timwi
6

Preferensi saya, untuk yang satu ini, akan menggunakan cast yang aman ke string jika objek yang disimpan dengan kunci bukan satu. Menggunakan ToString()mungkin tidak memberikan hasil yang Anda inginkan.

var y = Session["key"] as string ?? "none";

Seperti yang dikatakan @Jon Skeet, jika Anda sering melakukan ini sebagai metode ekstensi atau, lebih baik, namun mungkin metode ekstensi dalam hubungannya dengan kelas SessionWrapper yang diketik dengan kuat. Bahkan tanpa metode ekstensi, pembungkus yang diketik dengan kuat mungkin merupakan ide yang bagus.

public class SessionWrapper
{
    private HttpSessionBase Session { get; set; }

    public SessionWrapper( HttpSessionBase session )
    {
        Session = session;
    }

    public SessionWrapper() : this( HttpContext.Current.Session ) { }

    public string Key
    {
         get { return Session["key"] as string ?? "none";
    }

    public int MaxAllowed
    {
         get { return Session["maxAllowed"] as int? ?? 10 }
    }
}

Digunakan sebagai

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;
tvanfosson.dll
sumber
3

membuat fungsi tambahan

public static String GetValue( string key, string default )
{
    if ( Session[ key ] == null ) { return default; }
    return Session[ key ].toString();
}


string y = GetValue( 'key', 'none' );
scibuff
sumber
2

Jawaban Skeet adalah yang terbaik - khususnya menurut saya jawabannya ToStringOrNull()cukup elegan dan paling sesuai dengan kebutuhan Anda. Saya ingin menambahkan satu opsi lagi ke daftar metode ekstensi:

Kembalikan objek asli atau nilai string default untuk null :

// Method:
public static object OrNullAsString(this object input, string defaultValue)
{
    if (defaultValue == null)
        throw new ArgumentNullException("defaultValue");
    return input == null ? defaultValue : input;
}

// Example:
var y = Session["key"].OrNullAsString("defaultValue");

Gunakan varuntuk nilai yang dikembalikan karena akan kembali sebagai tipe masukan asli, hanya sebagai string default ketikanull

one.beat.consumer
sumber
Mengapa membuang pengecualian null defaultValuejika tidak diperlukan (yaitu input != null)?
Attila
Sebuah input != nulleval akan kembali objek sebagai sendiri. input == nullmengembalikan string yang disediakan sebagai param. oleh karena itu ada kemungkinan seseorang dapat memanggil .OnNullAsString(null)- tetapi tujuannya (meskipun metode ekstensi jarang berguna) adalah untuk memastikan Anda mendapatkan objek kembali atau string default ... tidak pernah null
one.beat.consumer
The input!=nullSkenario hanya akan kembali masukan jika defaultValue!=nulljuga memegang; jika tidak maka akan melempar ArgumentNullException.
Attila
0

Ini adalah "operator Elvis" tipe kecil saya yang aman untuk versi .NET yang tidak mendukung?.

public class IsNull
{
    public static O Substitute<I,O>(I obj, Func<I,O> fn, O nullValue=default(O))
    {
        if (obj == null)
            return nullValue;
        else
            return fn(obj);
    }
}

Argumen pertama adalah objek yang diuji. Kedua adalah fungsinya. Dan ketiga adalah nilai nol. Jadi untuk kasus Anda:

IsNull.Substitute(Session["key"],s=>s.ToString(),"none");

Ini juga sangat berguna untuk tipe nullable. Sebagai contoh:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
Tomaz Stih
sumber