Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Nota
Artikel ini khusus untuk .NET Framework. Ini tidak berlaku untuk implementasi .NET yang lebih baru, termasuk .NET 6 dan versi yang lebih baru.
Kontrak kode menyediakan cara untuk menentukan prasyarat, pascakondisi, dan invarian objek dalam kode .NET Framework. Prasyarat adalah persyaratan yang harus dipenuhi saat memasukkan metode atau properti. Pasca-kondisi menjelaskan harapan saat eksekusi metode atau kode properti selesai. Invarian objek menjelaskan status yang diharapkan untuk kelas yang dalam keadaan baik.
Nota
Kontrak kode tidak didukung di .NET 5+ (termasuk versi .NET Core). Pertimbangkan untuk menggunakan jenis referensi yang bisa kosong sebagai gantinya.
Kontrak kode mencakup kelas untuk menandai kode Anda, penganalisis statis untuk analisis waktu kompilasi, dan penganalisis runtime. Kelas untuk kontrak kode dapat ditemukan di System.Diagnostics.Contracts namespace.
Manfaat kontrak kode mencakup hal-hal berikut:
Pengujian yang ditingkatkan: Kontrak kode menyediakan verifikasi kontrak statis, pemeriksaan runtime, dan pembuatan dokumentasi.
Alat pengujian otomatis: Anda dapat menggunakan kontrak kode untuk menghasilkan pengujian unit yang lebih bermakna dengan memfilter argumen pengujian yang tidak berarti yang tidak memenuhi prasyarat.
Verifikasi statis: Pemeriksa statis dapat memutuskan apakah ada pelanggaran kontrak tanpa menjalankan program. Ini memeriksa kontrak implisit, seperti dereferensi null dan batas array, dan kontrak eksplisit.
Dokumentasi referensi: Generator dokumentasi menambah file dokumentasi XML yang ada dengan informasi kontrak. Ada juga lembar gaya yang dapat digunakan dengan Sandcastle sehingga halaman dokumentasi yang dihasilkan memiliki bagian kontrak.
Semua bahasa .NET Framework dapat segera memanfaatkan kontrak; Anda tidak perlu menulis pengurai atau pengkompilasi khusus. Add-in Visual Studio memungkinkan Anda menentukan tingkat analisis kontrak kode yang akan dilakukan. Penganalisis dapat mengonfirmasi bahwa kontrak terbentuk dengan baik (pemeriksaan jenis dan resolusi nama) dan dapat menghasilkan bentuk kontrak yang dikompilasi dalam format bahasa perantara umum (CIL). Kontrak penulisan di Visual Studio memungkinkan Anda memanfaatkan IntelliSense standar yang disediakan oleh alat ini.
Sebagian besar metode di kelas kontrak dikompilasi secara kondisional; artinya, pengkompilasi memancarkan panggilan ke metode ini hanya ketika Anda menentukan simbol khusus, CONTRACTS_FULL, dengan menggunakan direktif #define . CONTRACTS_FULL memungkinkan Anda menulis kontrak dalam kode Anda tanpa menggunakan #ifdef arahan; Anda dapat menghasilkan build yang berbeda, beberapa dengan kontrak, dan beberapa tanpa.
Untuk alat dan instruksi terperinci untuk menggunakan kontrak kode, lihat Kontrak Kode di situs marketplace Visual Studio.
Prasyarat
Anda dapat mengekspresikan prasyarat dengan menggunakan metode .Contract.Requires Prasyarat menentukan status ketika metode dipanggil. Mereka umumnya digunakan untuk menentukan nilai parameter yang valid. Semua anggota yang disebutkan dalam prasyarat harus setidaknya sama dapat diaksesnya dengan metode itu sendiri; jika tidak, prasyarat tersebut mungkin tidak akan dipahami oleh semua pemanggil metode. Kondisi harus tidak memiliki efek samping. Perilaku pada waktu proses dari prasyarat yang gagal ditentukan oleh analisis waktu proses.
Misalnya, prasyarat berikut mengekspresikan bahwa parameter x harus non-null.
Contract.Requires(x != null);
Jika kode Anda harus melempar pengecualian tertentu ketika kegagalan prasyarat terjadi, Anda dapat menggunakan overload generik Requires sebagai berikut.
Contract.Requires<ArgumentNullException>(x != null, "x");
Warisan Memerlukan Pernyataan
Sebagian besar kode berisi beberapa validasi parameter dalam bentuk if-then-throw kode. Alat kontrak mengenali pernyataan ini sebagai prasyarat dalam kasus berikut:
Pernyataan-pernyataan ditempatkan sebelum pernyataan lain dalam suatu metode.
Seluruh set pernyataan tersebut diikuti dengan panggilan metode Contract yang eksplisit, seperti panggilan ke metode Requires, Ensures, EnsuresOnThrow, atau EndContractBlock.
Ketika if-then-throw pernyataan muncul dalam formulir ini, alat mengenalinya sebagai pernyataan warisan.requires Jika tidak ada kontrak lain yang mengikuti urutan if-then-throw, akhiri kode dengan metode Contract.EndContractBlock.
if (x == null) throw new ...
Contract.EndContractBlock(); // All previous "if" checks are preconditions
Perhatikan bahwa kondisi dalam pengujian sebelumnya adalah prasyarat yang dinegasikan. (Prasyarat aktual adalah x != null.) Prasyarat yang dinegasikan sangat dibatasi: Prasyarat harus ditulis seperti yang ditunjukkan dalam contoh sebelumnya; artinya, tidak boleh berisi else klausul, dan isi then klausul harus berupa satu throw pernyataan. Pengujian if tunduk pada aturan kemurnian dan visibilitas (lihat Panduan Penggunaan), tetapi throw ekspresi hanya tunduk pada aturan kemurnian. Namun, jenis pengecualian yang dilemparkan harus sama terlihatnya dengan metode di mana kontrak terjadi.
Pascakondisi
Pascakondisi adalah kontrak untuk keadaan suatu metode saat berakhir. Pascakondisi diperiksa tepat sebelum keluar dari metode. Perilaku runtime pascakondisi yang gagal ditentukan oleh penganalisis runtime.
Tidak seperti prasyarat, pascakondisi dapat mengacu pada anggota dengan visibilitas yang lebih rendah. Klien mungkin tidak dapat memahami atau menggunakan beberapa informasi yang dinyatakan oleh pascakondisi menggunakan status privat, tetapi ini tidak memengaruhi kemampuan klien untuk menggunakan metode dengan benar.
Kondisi Pasca Standar
Anda dapat mengekspresikan pascakondisi standar dengan menggunakan metode Ensures. Pascakondisi menyatakan kondisi yang harus dipenuhi true setelah penghentian metode secara normal.
Contract.Ensures(this.F > 0);
Pascakondisi Luar Biasa
Pascakondisi luar biasa adalah pascakondisi yang seharusnya true ketika pengecualian tertentu dilemparkan oleh metode . Anda dapat menentukan pascakondisi ini dengan menggunakan Contract.EnsuresOnThrow metode , seperti yang ditunjukkan contoh berikut.
Contract.EnsuresOnThrow<T>(this.F > 0);
Argumen adalah kondisi yang harus true setiap kali pengecualian yang merupakan subjenis T dilemparkan.
Ada beberapa jenis pengecualian yang sulit digunakan dalam pascakondisi yang luar biasa. Misalnya, menggunakan jenis Exception untuk T memerlukan metode untuk menjamin kondisi terlepas dari jenis pengecualian yang dilemparkan, bahkan jika itu adalah stack overflow atau pengecualian lain yang tidak dapat dikontrol. Anda harus menggunakan pascakondisi luar biasa hanya untuk pengecualian tertentu yang mungkin dilemparkan ketika anggota dipanggil, misalnya, ketika InvalidTimeZoneException dilemparkan untuk TimeZoneInfo panggilan metode.
Pascakondisi Khusus
Metode berikut hanya dapat digunakan dalam pascakondisi:
Anda dapat merujuk ke nilai pengembalian metode dalam pascakondisi dengan menggunakan ekspresi
Contract.Result<T>(), di manaTdigantikan oleh jenis pengembalian metode. Ketika pengkompilasi tidak dapat menyimpulkan jenisnya, Anda harus secara eksplisit menyediakannya. Misalnya, pengkompilasi C# tidak dapat menyimpulkan jenis untuk metode yang tidak mengambil argumen apa pun, sehingga memerlukan pascakondisi berikut:Contract.Ensures(0 <Contract.Result<int>())Metode dengan jenisvoidpengembalian tidak dapat merujukContract.Result<T>()dalam pascakondisi mereka.Nilai prestat dalam pascakondisi mengacu pada nilai ekspresi di awal metode atau properti. Ini menggunakan ekspresi
Contract.OldValue<T>(e), di manaTadalah jenise. Anda dapat menghilangkan argumen jenis generik setiap kali pengkompilasi dapat menyimpulkan jenisnya. (Misalnya, pengkompilasi C# selalu menyimpulkan jenis karena membutuhkan argumen.) Ada beberapa batasan tentang apa yang dapat terjadi diedan konteks di mana ekspresi lama mungkin muncul. Ekspresi lama tidak boleh berisi ekspresi lama lainnya. Yang terpenting, ekspresi lama harus merujuk ke nilai yang ada dalam status prasyarat metode. Dengan kata lain, itu harus menjadi ekspresi yang dapat dievaluasi selama prasyarat metode adalahtrue. Berikut adalah beberapa contoh aturan tersebut.Nilai harus ada dalam status prasyarat metode. Untuk mereferensikan bidang pada objek, prasyarat harus menjamin bahwa objek selalu non-null.
Anda tidak dapat merujuk ke nilai pengembalian metode dalam ekspresi lama:
Contract.OldValue(Contract.Result<int>() + x) // ERRORAnda tidak dapat merujuk ke
outparameter dalam ekspresi lama.Ekspresi lama tidak dapat bergantung pada variabel terikat kuantifier jika rentang kuantifier bergantung pada nilai pengembalian metode:
Contract.ForAll(0, Contract.Result<int>(), i => Contract.OldValue(xs[i]) > 3); // ERROREkspresi yang sudah usang tidak dapat mengacu pada parameter delegasi anonim dalam panggilan ForAll atau Exists kecuali jika digunakan sebagai pengindeks atau argumen untuk panggilan metode:
Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK Contract.ForAll(0, xs.Length, i => Contract.OldValue(i) > 3); // ERROREkspresi lama tidak dapat muncul dalam tubuh delegasi anonim jika nilai ekspresi lama bergantung pada salah satu parameter delegasi anonim, kecuali delegasi anonim adalah argumen untuk metode ForAll atau Exists.
Method(... (T t) => Contract.OldValue(... t ...) ...); // ERROROutparameter menyajikan masalah karena kontrak muncul sebelum isi metode, dan sebagian besar kompilator tidak mengizinkan referensi keoutparameter dalam pascakondisi. Untuk mengatasi masalah ini, Contract kelas menyediakan ValueAtReturn metode , yang memungkinkan pascakondisi berdasarkanoutparameter.public void OutParam(out int x) { Contract.Ensures(Contract.ValueAtReturn(out x) == 3); x = 3; }Seperti halnya OldValue metode , Anda dapat menghilangkan parameter jenis generik setiap kali pengkompilasi dapat menyimpulkan jenisnya. Penulis ulang kontrak menggantikan panggilan metode dengan nilai parameter
out. Metode ValueAtReturn ini mungkin hanya muncul dalam pascakondisi. Argumen ke metode harus berupaoutparameter atau bidang parameter strukturout. Yang terakhir juga berguna ketika mengacu pada elemen dalam kondisi akhir dari konstruktor struktur.Nota
Saat ini, perangkat analisis kontrak kode tidak memeriksa apakah parameter
outdiinisialisasi dengan benar dan mengabaikan penyebutannya dalam pascakondisi. Oleh karena itu, dalam contoh sebelumnya, jika baris setelah kontrak telah menggunakan nilaixdaripada menetapkan bilangan bulat padanya, pengkompilasi tidak akan mengeluarkan kesalahan yang benar. Namun, pada build di mana simbol pra-pemroses CONTRACTS_FULL tidak didefinisikan (seperti build rilis), kompiler akan mengeluarkan error.
Ketetapan
Invarian objek adalah kondisi yang harus benar untuk setiap instans kelas saat objek tersebut diakses oleh klien. Mereka mengekspresikan kondisi di mana objek dianggap benar.
Metode invariant diidentifikasi dengan ditandai dengan ContractInvariantMethodAttribute atribut . Metode invarian tidak boleh berisi kode kecuali untuk urutan panggilan ke Invariant metode , yang masing-masing menentukan invarian individu, seperti yang ditunjukkan dalam contoh berikut.
[ContractInvariantMethod]
protected void ObjectInvariant ()
{
Contract.Invariant(this.y >= 0);
Contract.Invariant(this.x > this.y);
...
}
Invarian didefinisikan secara kondisional oleh simbol preprosesor CONTRACTS_FULL. Selama pemeriksaan runtime, invarian diperiksa di akhir setiap metode publik. Jika invarian menyebutkan metode publik di kelas yang sama, pemeriksaan invarian yang biasanya akan terjadi di akhir metode publik tersebut dinonaktifkan. Sebaliknya, pemeriksaan hanya terjadi di akhir panggilan metode terluar ke kelas tersebut. Ini juga terjadi jika kelas diakses ulang karena adanya pemanggilan metode di kelas lain. Invarian tidak diperiksa untuk finalizer objek dan implementasi IDisposable.Dispose.
Panduan Penggunaan
Pemesanan Berdasarkan Kontrak
Tabel berikut menunjukkan urutan elemen yang harus Anda gunakan saat menulis kontrak metode.
If-then-throw statements |
Prasyarat publik yang kompatibel secara mundur |
|---|---|
| Requires | Semua prasyarat publik. |
| Ensures | Semua pascakondisi publik (normal). |
| EnsuresOnThrow | Semua kondisi pasca khusus publik. |
| Ensures | Semua pascakondisi (normal) yang bersifat privat/internal. |
| EnsuresOnThrow | Semua pascakondisi privat/internal yang luar biasa. |
| EndContractBlock | Jika menggunakan if-then-throw prasyarat gaya tanpa kontrak lain, lakukan pemanggilan ke EndContractBlock untuk menunjukkan bahwa semua pengecekan 'jika' sebelumnya adalah prasyarat. |
Kemurnian
Semua metode yang dipanggil dalam kontrak harus murni; artinya, mereka tidak boleh memperbarui status yang sudah ada sebelumnya. Metode murni diizinkan untuk memodifikasi objek yang telah dibuat setelah entri ke dalam metode murni.
Alat kontrak kode saat ini mengasumsikan bahwa elemen kode berikut murni:
Metode yang ditandai dengan PureAttribute.
Jenis yang ditandai dengan PureAttribute (atribut berlaku untuk semua metode jenis).
Properti mendapatkan aksesor.
Operator (metode statis yang namanya dimulai dengan "op", dan yang memiliki satu atau dua parameter dan jenis pengembalian yang bukan void).
Metode apa pun yang namanya sepenuhnya memenuhi syarat dimulai dengan "System.Diagnostics.Contracts.Contract", "System.String", "System.IO.Path", atau "System.Type".
Setiap delegasi yang dipanggil, asalkan tipe delegasi itu sendiri memiliki atribut PureAttribute. Jenis-jenis delegasi System.Predicate<T> dan System.Comparison<T> dianggap murni.
Keterlihatan
Semua anggota yang disebutkan dalam kontrak harus memiliki tingkat visibilitas yang minimal setara dengan metode tempat mereka disebutkan. Misalnya, bidang privat tidak dapat disebutkan dalam prasyarat untuk metode publik; klien tidak dapat memvalidasi kontrak tersebut sebelum mereka memanggil metode . Namun, jika bidang ditandai dengan ContractPublicPropertyNameAttribute, bidang tersebut dikecualikan dari aturan ini.
Contoh
Contoh berikut menunjukkan penggunaan kontrak kode.
#define CONTRACTS_FULL
using System;
using System.Diagnostics.Contracts;
// An IArray is an ordered collection of objects.
[ContractClass(typeof(IArrayContract))]
public interface IArray
{
// The Item property provides methods to read and edit entries in the array.
Object this[int index]
{
get;
set;
}
int Count
{
get;
}
// Adds an item to the list.
// The return value is the position the new element was inserted in.
int Add(Object value);
// Removes all items from the list.
void Clear();
// Inserts value into the array at position index.
// index must be non-negative and less than or equal to the
// number of elements in the array. If index equals the number
// of items in the array, then value is appended to the end.
void Insert(int index, Object value);
// Removes the item at position index.
void RemoveAt(int index);
}
[ContractClassFor(typeof(IArray))]
internal abstract class IArrayContract : IArray
{
int IArray.Add(Object value)
{
// Returns the index in which an item was inserted.
Contract.Ensures(Contract.Result<int>() >= -1);
Contract.Ensures(Contract.Result<int>() < ((IArray)this).Count);
return default(int);
}
Object IArray.this[int index]
{
get
{
Contract.Requires(index >= 0);
Contract.Requires(index < ((IArray)this).Count);
return default(int);
}
set
{
Contract.Requires(index >= 0);
Contract.Requires(index < ((IArray)this).Count);
}
}
public int Count
{
get
{
Contract.Requires(Count >= 0);
Contract.Requires(Count <= ((IArray)this).Count);
return default(int);
}
}
void IArray.Clear()
{
Contract.Ensures(((IArray)this).Count == 0);
}
void IArray.Insert(int index, Object value)
{
Contract.Requires(index >= 0);
Contract.Requires(index <= ((IArray)this).Count); // For inserting immediately after the end.
Contract.Ensures(((IArray)this).Count == Contract.OldValue(((IArray)this).Count) + 1);
}
void IArray.RemoveAt(int index)
{
Contract.Requires(index >= 0);
Contract.Requires(index < ((IArray)this).Count);
Contract.Ensures(((IArray)this).Count == Contract.OldValue(((IArray)this).Count) - 1);
}
}
#Const CONTRACTS_FULL = True
Imports System.Diagnostics.Contracts
' An IArray is an ordered collection of objects.
<ContractClass(GetType(IArrayContract))> _
Public Interface IArray
' The Item property provides methods to read and edit entries in the array.
Default Property Item(ByVal index As Integer) As [Object]
ReadOnly Property Count() As Integer
' Adds an item to the list.
' The return value is the position the new element was inserted in.
Function Add(ByVal value As Object) As Integer
' Removes all items from the list.
Sub Clear()
' Inserts value into the array at position index.
' index must be non-negative and less than or equal to the
' number of elements in the array. If index equals the number
' of items in the array, then value is appended to the end.
Sub Insert(ByVal index As Integer, ByVal value As [Object])
' Removes the item at position index.
Sub RemoveAt(ByVal index As Integer)
End Interface 'IArray
<ContractClassFor(GetType(IArray))> _
Friend MustInherit Class IArrayContract
Implements IArray
Function Add(ByVal value As Object) As Integer Implements IArray.Add
' Returns the index in which an item was inserted.
Contract.Ensures(Contract.Result(Of Integer)() >= -1) '
Contract.Ensures(Contract.Result(Of Integer)() < CType(Me, IArray).Count) '
Return 0
End Function 'IArray.Add
Default Property Item(ByVal index As Integer) As Object Implements IArray.Item
Get
Contract.Requires(index >= 0)
Contract.Requires(index < CType(Me, IArray).Count)
Return 0 '
End Get
Set(ByVal value As [Object])
Contract.Requires(index >= 0)
Contract.Requires(index < CType(Me, IArray).Count)
End Set
End Property
Public ReadOnly Property Count() As Integer Implements IArray.Count
Get
Contract.Requires(Count >= 0)
Contract.Requires(Count <= CType(Me, IArray).Count)
Return 0 '
End Get
End Property
Sub Clear() Implements IArray.Clear
Contract.Ensures(CType(Me, IArray).Count = 0)
End Sub
Sub Insert(ByVal index As Integer, ByVal value As [Object]) Implements IArray.Insert
Contract.Requires(index >= 0)
Contract.Requires(index <= CType(Me, IArray).Count) ' For inserting immediately after the end.
Contract.Ensures(CType(Me, IArray).Count = Contract.OldValue(CType(Me, IArray).Count) + 1)
End Sub
Sub RemoveAt(ByVal index As Integer) Implements IArray.RemoveAt
Contract.Requires(index >= 0)
Contract.Requires(index < CType(Me, IArray).Count)
Contract.Ensures(CType(Me, IArray).Count = Contract.OldValue(CType(Me, IArray).Count) - 1)
End Sub
End Class