Atribut untuk analisis statis null-state yang ditafsirkan oleh compiler C#
Dalam konteks yang diaktifkan nullable, compiler melakukan analisis statis kode untuk menentukan status null dari semua variabel jenis referensi:
- not-null: Analisis statis menentukan bahwa variabel memiliki nilai non-null.
- mayben-null: Analisis statis tidak dapat menentukan bahwa variabel diberi nilai non-null.
Status ini memungkinkan compiler untuk memberi peringatan saat Anda dapat mendereferensikan nilai null, melemparkan System.NullReferenceException. Atribut ini menyediakan compiler dengan informasi semantik tentang status null dari argumen, nilai pengembalian, dan anggota objek berdasarkan status argumen dan nilai yang dikembalikan. Compiler memberi peringatan yang lebih akurat saat API Anda telah dianotasi dengan benar dengan informasi semantik ini.
Artikel ini memberi deskripsi singkat tentang setiap atribut jenis referensi yang dapat diubah ke null dan cara menggunakannya.
Mari kita mulai dengan contoh. Bayangkan pustaka Anda memiliki API berikut untuk mengambil string sumber daya. Metode ini awalnya dikompilasi dalam konteks terlupakan nullable:
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Contoh sebelumnya mengikuti pola Try*
yang akrab di .NET. Ada dua parameter referensi untuk API ini: key
dan message
. API ini memiliki aturan berikut yang berkaitan dengan status null dari parameter ini:
- Pemanggil tidak boleh meneruskan
null
sebagai argumen untukkey
. - Pemanggil dapat meneruskan variabel yang nilainya
null
sebagai argumen untukmessage
. - Jika metode
TryGetMessage
mengembalikantrue
, nilaimessage
tidak null. Jika nilai yang dikembalikan adalahfalse,
nilaimessage
adalah null.
Aturan untuk key
dapat diekspresikan dengan tepat: key
harus merupakan jenis referensi yang tidak dapat diubah ke null. Parameter message
lebih kompleks. Ini memungkinkan variabel yang null
sebagai argumen, tetapi menjamin, pada keberhasilan, bahwa argumen out
bukan null
. Untuk skenario ini, Anda memerlukan kosakata yang lebih kaya untuk menggambarkan harapan. Atribut NotNullWhen
, yang dijelaskan di bawah ini menjelaskan status null untuk argumen yang digunakan untuk parameter message
.
Catatan
Menambahkan atribut ini memberi compiler informasi lebih lanjut tentang aturan untuk API Anda. Saat kode panggilan dikompilasi dalam konteks yang diaktifkan nullable, compiler akan memperingatkan pemanggil saat mereka melanggar aturan tersebut. Atribut ini tidak mengaktifkan lebih banyak pemeriksaan pada implementasi Anda.
Atribut | Kategori | Makna |
---|---|---|
AllowNull | Prasyarat | Parameter, bidang, atau properti yang tidak dapat diubah ke null mungkin null. |
DisallowNull | Prasyarat | Parameter, bidang, atau properti yang dapat diubah ke null tidak boleh null. |
MaybeNull | Pascakondisi | Parameter, bidang, properti, atau nilai pengembalian yang tidak dapat diubah ke null mungkin null. |
NotNull | Pascakondisi | Parameter, bidang, properti, atau nilai pengembalian yang dapat diubah ke null mungkin null. |
MaybeNullWhen | Pascakondisi bersyarat | Argumen yang tidak dapat diubah ke null mungkin null saat metode mengembalikan nilai bool yang ditentukan. |
NotNullWhen | Pascakondisi bersyarat | Argumen yang tidak dapat diubah ke null mungkin null saat metode mengembalikan nilai bool yang ditentukan. |
NotNullIfNotNull | Pascakondisi bersyarat | Nilai pengembalian, properti, atau argumen tidak null jika argumen untuk parameter yang ditentukan bukan null. |
MemberNotNull | Metode dan metode pembantu properti | Anggota yang tercantum tidak akan null saat metode kembali. |
MemberNotNullWhen | Metode dan metode pembantu properti | Argumen yang tidak dapat diubah ke null mungkin null saat metode mengembalikan nilai bool yang ditentukan. |
DoesNotReturn | Kode yang tidak dapat dijangkau | Metode atau properti tidak pernah kembali. Dengan kata lain, itu selalu melemparkan pengecualian. |
DoesNotReturnIf | Kode yang tidak dapat dijangkau | Metode atau properti ini tidak pernah mengembalikan jika parameter bool terkait memiliki nilai yang ditentukan. |
Deskripsi sebelumnya adalah referensi cepat untuk apa yang dilakukan setiap atribut. Bagian berikut menjelaskan perilaku dan arti atribut ini secara lebih menyeluruh.
Prasyarat: AllowNull
dan DisallowNull
Pertimbangkan properti baca/tulis yang tidak pernah mengembalikan null
karena memiliki nilai default yang wajar. Pemanggil meneruskan null
ke aksesor yang ditetapkan saat mengaturnya ke nilai default tersebut. Misalnya, pertimbangkan sistem olahpesan yang meminta nama layar di ruang obrolan. Jika tidak ada yang disediakan, sistem menghasilkan nama acak:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
Saat Anda mengompilasi kode sebelumnya dalam konteks samar-samar, semuanya baik-baik saja. Setelah Anda mengaktifkan jenis referensi nullable, properti ScreenName
menjadi referensi yang tidak dapat diubah ke null. Itu benar untuk aksesor get
: tidak pernah mengembalikan null
. Pemanggil tidak perlu memeriksa properti yang dikembalikan untuk null
. Tetapi sekarang mengatur properti untuk null
menghasilkan peringatan. Untuk mendukung jenis kode ini, Anda menambah System.Diagnostics.CodeAnalysis.AllowNullAttribute atribut ke properti, seperti yang ditunjukkan dalam kode berikut:
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
Anda mungkin perlu menambah direktif using
untuk System.Diagnostics.CodeAnalysis menggunakan atribut ini dan atribut lain yang dibahas dalam artikel ini. Atribut diterapkan ke properti, bukan aksesor set
. Atribut AllowNull
menentukan pra-kondisi, dan hanya berlaku untuk argumen. Aksesor get
memiliki nilai pengembalian, tetapi tidak ada parameter. Oleh karena itu, atribut AllowNull
hanya berlaku untuk aksesor set
.
Contoh sebelumnya menunjukkan apa yang harus dicari saat menambahkan atribut AllowNull
pada argumen:
- Kontrak umum untuk variabel tersebut adalah bahwa seharusnya tidak
null
, jadi Anda menginginkan jenis referensi yang tidak dapat diubah ke null. - Ada skenario bagi pemanggil untuk meneruskan
null
sebagai argumen, meskipun bukan penggunaan yang paling umum.
Paling sering Anda memerlukan atribut ini untuk properti, atau argumen in
, out
, dan ref
. Atribut AllowNull
adalah pilihan terbaik saat variabel biasanya non-null, tetapi Anda perlu mengizinkan null
sebagai prasyarat.
Berbeda dengan skenario untuk menggunakan DisallowNull
: Anda menggunakan atribut ini untuk menentukan bahwa argumen dari jenis referensi yang dapat diubah ke null tidak boleh null
. Pertimbangkan properti di mana null
adalah nilai default, tetapi klien hanya dapat mengaturnya ke nilai non-null. Pertimbangkan gambar berikut:
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
Kode sebelumnya adalah cara terbaik untuk mengekspresikan desain Anda bahwa ReviewComment
bisa null
tetapi tidak dapat diatur ke null
. Setelah kode ini sadar null, Anda dapat mengekspresikan konsep ini dengan lebih jelas kepada pemanggil menggunakan System.Diagnostics.CodeAnalysis.DisallowNullAttribute:
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
Dalam konteks nullable, ReviewComment
get
aksesor dapat mengembalikan nilai default .null
Compiler memperingatkan bahwa compiler harus diperiksa sebelum akses. Selain itu, ini memperingatkan pemanggil bahwa, meskipun bisa jadi null
, pemanggil tidak boleh secara eksplisit mengaturnya ke null
. Atribut DisallowNull
ini juga menentukan pra-kondisi, tidak memengaruhi pengakses get
. Anda menggunakan atribut DisallowNull
saat mengamati karakteristik ini tentang:
- Variabel dapat
null
dalam skenario inti, sering kali saat pertama kali dibuat. - Variabel tidak boleh diatur secara eksplisit ke
null
.
Situasi ini umum dalam kode yang awalnya tidak sadar null. Mungkin properti objek diatur dalam dua operasi inisialisasi yang berbeda. Mungkin beberapa properti diatur hanya setelah beberapa pekerjaan asinkron selesai.
Atribut AllowNull
dan DisallowNull
memungkinkan Anda menentukan bahwa prasyarat pada variabel mungkin tidak cocok dengan anotasi nullable pada variabel tersebut. Ini memberikan detail lebih lanjut tentang karakteristik API Anda. Informasi tambahan ini membantu pemanggil menggunakan API Anda dengan benar. Ingat bahwa Anda menentukan prasyarat menggunakan atribut berikut:
- AllowNull: Argumen yang tidak dapat diubah ke null mungkin null.
- DisallowNull: Argumen nullable tidak boleh null.
Pascakondisi: MaybeNull
dan NotNull
Misalkan Anda memiliki metode dengan tanda tangan berikut:
public Customer FindCustomer(string lastName, string firstName)
Anda mungkin telah menulis metode seperti ini untuk mengembalikan null
saat nama yang dicari tidak ditemukan. null
jelas menunjukkan bahwa rekaman tidak ditemukan. Dalam contoh ini, Anda mungkin akan mengubah jenis pengembalian dari Customer
ke Customer?
. Mendeklarasikan nilai yang dikembalikan sebagai jenis referensi yang dapat diubah ke null menentukan niat API ini dengan jelas:
public Customer? FindCustomer(string lastName, string firstName)
Untuk alasan yang tercakup dalam Generic nullability, teknik tersebut mungkin tidak menghasilkan analisis statis yang cocok dengan API Anda. Anda mungkin memiliki metode generik yang mengikuti pola serupa:
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Metode mengembalikan null
saat item yang dicari tidak ditemukan. Anda dapat mengklarifikasi bahwa metode mengembalikan null
saat item tidak ditemukan dengan menambah anotasi MaybeNull
ke pengembalian metode:
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Kode sebelumnya memberi tahu pemanggil bahwa nilai yang dikembalikan mungkin benar-benar null. Ini juga menginformasikan compiler bahwa metode dapat mengembalikan null
ekspresi meskipun jenisnya tidak dapat diubah ke null. Saat Anda memiliki metode generik yang mengembalikan instans parameter jenisnya, T
, Anda dapat mengekspresikan bahwa itu tidak pernah mengembalikan null
menggunakan atribut NotNull
.
Anda juga dapat menentukan bahwa nilai yang dikembalikan atau argumen tidak null meskipun jenisnya adalah jenis referensi yang dapat diubah ke null. Metode berikut adalah metode pembantu yang melemparkan jika argumen pertamanya adalah null
:
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
Anda dapat memanggil rutinitas ini sebagai berikut:
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
Setelah mengaktifkan jenis referensi null, Anda ingin memastikan bahwa kode sebelumnya dikompilasi tanpa peringatan. Saat metode kembali, parameter value
dijamin tidak null. Namun, dapat diterima untuk memanggil ThrowWhenNull
dengan referensi null. Anda dapat membuat value
jenis referensi yang dapat diubah ke null, dan menambahkan pasca-kondisi NotNull
ke deklarasi parameter:
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
Kode sebelumnya mengekspresikan kontrak yang ada dengan jelas: Pemanggil dapat meneruskan variabel dengan nilai null
, tetapi argumen dijamin tidak akan pernah null jika metode kembali tanpa melemparkan pengecualian.
Anda menentukan pascakondisi tanpa syarat menggunakan atribut berikut:
- MaybeNull: Nilai pengembalian yang tidak dapat diubah ke null mungkin null.
- NotNull: Nilai pengembalian nullable tidak akan pernah null.
Kondisi pasca kondisi: NotNullWhen
, MaybeNullWhen
, dan NotNullIfNotNull
Anda mungkin terbiasa dengan string
metode String.IsNullOrEmpty(String). Metode ini mengembalikan true
saat argumen null atau string kosong. Ini adalah bentuk pemeriksaan null: Pemanggil tidak perlu memeriksa null argumen jika metode mengembalikan false
. Untuk membuat metode seperti sadar null ini, Anda akan mengatur argumen ke jenis referensi yang dapat diubah ke null, dan menambahkan NotNullWhen
atribut :
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
Itu menginformasikan compiler bahwa kode apa pun di mana nilai false
yang dikembalikan tidak memerlukan pemeriksaan null. Penambahan atribut menginformasikan analisis statis compiler bahwa IsNullOrEmpty
melakukan pemeriksaan null yang dibutuhkan: saat mengembalikan false
, argumen bukan null
.
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
Metode String.IsNullOrEmpty(String) ini akan diannotasi seperti yang ditunjukkan di atas untuk .NET Core 3.0. Anda mungkin memiliki metode serupa di basis kode Anda yang memeriksa status objek untuk nilai null. Compiler tidak akan mengenali metode pemeriksaan null kustom, dan Anda harus menambahkan anotasi sendiri. Saat Anda menambahkan atribut, analisis statis compiler tahu kapan variabel yang diuji telah diperiksa null.
Penggunaan lain untuk atribut ini adalah pola Try*
. Pascakondisi untuk argumen ref
dan out
dikomunikasikan melalui nilai yang dikembalikan. Pertimbangkan metode ini yang ditunjukkan sebelumnya (dalam konteks nonaktif null):
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Metode sebelumnya mengikuti idiom .NET yang khas: nilai pengembalian menunjukkan apakah message
diatur ke nilai yang ditemukan atau, jika tidak ada pesan yang ditemukan, ke nilai default. Jika metode mengembalikan true
, nilai message
tidak null; jika tidak, metode mengatur message
ke null.
Dalam konteks yang diaktifkan nullable, Anda dapat mengomunikasikan idiom tersebut NotNullWhen
menggunakan atribut . Saat Anda membuat anotasi parameter untuk jenis referensi yang dapat diubah ke null, buat message
menjadi string?
dan tambahkan atribut:
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
Dalam contoh sebelumnya, nilai message
diketahui tidak null saat TryGetMessage
mengembalikan true
. Anda harus membuat anotasi metode serupa dalam basis kode Anda dengan cara yang sama: argumen bisa sama dengan null
, dan diketahui tidak null saat metode mengembalikan true
.
Ada satu atribut akhir yang mungkin juga Anda butuhkan. Terkadang status null dari nilai yang dikembalikan tergantung pada status null dari satu atau beberapa argumen. Metode ini akan mengembalikan nilai non-null setiap kali argumen tertentu bukan null
. Untuk membuat anotasi metode ini dengan benar, Anda menggunakan atribut NotNullIfNotNull
. Pertimbangkan kode berikut:
string GetTopLevelDomainFromFullUrl(string url)
Jika argumen url
tidak null, outputnya bukan null
. Setelah referensi nullable diaktifkan, Anda perlu menambahkan lebih banyak anotasi jika API Anda mungkin menerima argumen null. Anda dapat membuat anotasi jenis pengembalian seperti yang ditunjukkan dalam kode berikut:
string? GetTopLevelDomainFromFullUrl(string? url)
Itu juga berfungsi, tetapi akan sering memaksa pemanggil untuk menerapkan pemeriksaan null
tambahan. Kontraknya adalah bahwa nilai pengembalian hanya akan null
saat argumen url
adalah null
. Untuk mengekspresikan kontrak tersebut, Anda akan membuat anotasi metode ini seperti yang ditunjukkan dalam kode berikut:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
Contoh sebelumnya menggunakan operator nameof
untuk parameter url
. Fitur tersebut tersedia di C# 11. Sebelum C# 11, Anda harus mengetikkan nama parameter sebagai string. Nilai yang dikembalikan dan argumen telah diannotasikan dengan ?
menunjukkan bahwa salah satunya bisa berupa null
. Atribut selanjutnya mengklarifikasi bahwa nilai yang dikembalikan tidak akan null saat argumen url
bukan null
.
Anda menentukan pascakondisi bersyarat menggunakan atribut ini:
- MaybeNullWhen: Argumen non-nullable dapat null saat metode mengembalikan nilai
bool
yang ditentukan. - NotNullWhen: Argumen nullable tidak dapat null saat metode mengembalikan nilai
bool
yang ditentukan. - NotNullIfNotNull: Nilai pengembalian bukan null jika argumen untuk parameter yang ditentukan bukan null.
Metode pembantu: MemberNotNull
dan MemberNotNullWhen
Atribut ini menentukan niat Anda saat Anda telah memfaktorkan ulang kode umum dari konstruktor ke dalam metode pembantu. Compiler C# menganalisis konstruktor dan penginisialisasi bidang untuk memastikan bahwa semua bidang referensi yang tidak dapat diubah ke null telah diinisialisasi sebelum setiap konstruktor kembali. Namun, compiler C# tidak melacak penetapan bidang melalui semua metode pembantu. Pomengompilasi mengeluarkan peringatan CS8618
saat bidang tidak diinisialisasi langsung di konstruktor, melainkan dalam metode pembantu. Anda menambah MemberNotNullAttribute ke deklarasi metode dan menentukan bidang yang diinisialisasi ke nilai non-null dalam metode. Misalnya, pertimbangkan contoh berikut:
public class Container
{
private string _uniqueIdentifier; // must be initialized.
private string? _optionalMessage;
public Container()
{
Helper();
}
public Container(string message)
{
Helper();
_optionalMessage = message;
}
[MemberNotNull(nameof(_uniqueIdentifier))]
private void Helper()
{
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
}
}
Anda dapat menentukan beberapa nama bidang sebagai argumen ke konstruktor atribut MemberNotNull
.
MemberNotNullWhenAttribute memiliki argumen bool
. Anda menggunakan MemberNotNullWhen
dalam situasi di mana metode pembantu Anda mengembalikan bool
yang menunjukkan apakah metode pembantu Anda menginisialisasi bidang.
Hentikan analisis nullable saat metode yang dipanggil melempar
Beberapa metode, biasanya pembantu pengecualian atau metode utilitas lainnya, selalu keluar dengan melemparkan pengecualian. Atau, pembantu dapat melemparkan pengecualian berdasarkan nilai argumen Boolean.
Dalam kasus pertama, Anda dapat menambahkan atribut DoesNotReturnAttribute ke deklarasi metode. Analisis status null compiler tidak memeriksa kode apa pun dalam metode yang mengikuti panggilan ke metode yang diannotasikan dengan DoesNotReturn
. Pertimbangkan metode ini:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
Compiler tidak mengeluarkan peringatan apa pun setelah panggilan ke FailFast
.
Dalam kasus kedua, Anda menambah atribut System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute ke parameter Boolean dari metode. Anda dapat mengubah contoh sebelumnya sebagai berikut:
private void FailFastIf([DoesNotReturnIf(true)] bool isNull)
{
if (isNull)
{
throw new InvalidOperationException();
}
}
public void SetFieldState(object? containedField)
{
FailFastIf(containedField == null);
// No warning: containedField can't be null here:
_field = containedField;
}
Saat nilai argumen cocok dengan nilai konstruktor, compiler tidak melakukan analisis DoesNotReturnIf
status null setelah metode tersebut.
Ringkasan
Menambahkan jenis referensi nullable menyediakan kosakata awal untuk menjelaskan ekspektasi API Anda untuk variabel yang bisa berupa null
. Atribut menyediakan kosakata yang lebih kaya untuk menggambarkan status variabel null sebagai prasyarat dan pascakondisi. Atribut ini lebih jelas menggambarkan harapan Anda dan memberikan pengalaman yang lebih baik bagi pengembang menggunakan API Anda.
Saat Anda memperbarui pustaka untuk konteks yang dapat diubah ke null, tambahkan atribut ini untuk memandu pengguna API Anda ke penggunaan yang benar. Atribut ini membantu Anda sepenuhnya menjelaskan status null argumen dan mengembalikan nilai.
- AllowNull: Bidang, parameter, atau properti yang tidak dapat diubah ke null mungkin null.
- DisallowNull: Bidang, parameter, atau properti yang dapat diubah ke null tidak boleh null.
- MaybeNull: Bidang, parameter, properti, atau nilai pengembalian yang tidak dapat diubah ke null mungkin null.
- NotNull: Parameter, bidang, properti, atau nilai pengembalian yang dapat diubah ke null tidak akan pernah null.
- MaybeNullWhen: Argumen non-nullable dapat null saat metode mengembalikan nilai
bool
yang ditentukan. - NotNullWhen: Argumen nullable tidak dapat null saat metode mengembalikan nilai
bool
yang ditentukan. - NotNullIfNotNull: Parameter, bidang, properti, atau nilai pengembalian bukan null jika argumen untuk parameter yang ditentukan bukan null.
- DoesNotReturn: Metode atau properti tidak pernah kembali. Dengan kata lain, itu selalu melemparkan pengecualian.
- DoesNotReturnIf: Metode atau properti ini tidak pernah kembali jika parameter
bool
memiliki nilai yang ditentukan.