Kod sözleşmeleri (.NET Framework)
Not
Bu makale .NET Framework'e özgüdür. .NET 6 ve sonraki sürümleri de dahil olmak üzere daha yeni .NET uygulamaları için geçerli değildir.
Kod sözleşmeleri .NET Framework kodunda önkoşulları, son önkoşulları ve nesne sabitlerini belirtmek için bir yol sağlar. Önkoşullar, bir yöntem veya özellik girilirken karşılanması gereken gereksinimlerdir. Son koşullarda, yöntem veya özellik kodundan çıkış sırasındaki beklentiler açıklanır. Nesne sabitleri, iyi durumda olan bir sınıf için beklenen durumu açıklar.
Not
Kod sözleşmeleri .NET 5+ sürümünde (.NET Core sürümleri dahil) desteklenmez. Bunun yerine Null atanabilir başvuru türlerini kullanmayı göz önünde bulundurun.
Kod anlaşmaları kodunuzu işaretlemeye yönelik sınıfları, derleme zamanı analizi için statik bir çözümleyiciyi ve çalışma zamanı çözümleyicisini içerir. Kod sözleşmelerinin sınıfları ad alanında System.Diagnostics.Contracts bulunabilir.
Kod sözleşmelerinin avantajları şunlardır:
Geliştirilmiş test: Kod sözleşmeleri statik sözleşme doğrulaması, çalışma zamanı denetimi ve belge oluşturma sağlar.
Otomatik test araçları: Önkoşulları karşılamayan anlamsız test bağımsız değişkenlerini filtreleyerek daha anlamlı birim testleri oluşturmak için kod sözleşmelerini kullanabilirsiniz.
Statik doğrulama: Statik denetleyici, programı çalıştırmadan herhangi bir sözleşme ihlali olup olmadığına karar verebilir. Null başvurular, dizi sınırları ve açık sözleşmeler gibi örtük sözleşmeleri denetler.
Başvuru belgeleri: Belge oluşturucu, mevcut XML belge dosyalarını sözleşme bilgileriyle genişletmektedir. Oluşturulan belge sayfalarının sözleşme bölümlerine sahip olması için Sandcastle ile kullanılabilecek stil sayfaları da vardır.
Tüm .NET Framework dilleri sözleşmelerden hemen yararlanabilir; özel bir ayrıştırıcı veya derleyici yazmanız gerekmez. Visual Studio eklentisi, gerçekleştirilecek kod sözleşmesi çözümleme düzeyini belirtmenize olanak tanır. Çözümleyiciler, sözleşmelerin iyi biçimlendirilmiş olduğunu onaylayabilir (tür denetimi ve ad çözümlemesi) ve ortak ara dil (CIL) biçiminde sözleşmelerin derlenmiş bir biçimini üretebilir. Visual Studio'da sözleşme yazma, araç tarafından sağlanan standart IntelliSense'in avantajlarından yararlanmanızı sağlar.
Sözleşme sınıfındaki yöntemlerin çoğu koşullu olarak derlenir; yani, derleyici yalnızca yönergesini kullanarak özel bir simge CONTRACTS_FULL tanımladığınızda bu yöntemlere #define
çağrılar yayar. CONTRACTS_FULL, yönergeleri kullanmadan #ifdef
kodunuzda sözleşmeler yazmanıza olanak tanır; bazıları sözleşmelerle ve bazıları olmadan farklı derlemeler oluşturabilirsiniz.
Kod sözleşmelerini kullanmaya yönelik araçlar ve ayrıntılı yönergeler için bkz . Visual Studio market sitesindeki Kod Sözleşmeleri .
Ön koşullar
yöntemini kullanarak Contract.Requires önkoşulları ifade edebilirsiniz. Önkoşullar, bir yöntem çağrıldığında durumu belirtir. Bunlar genellikle geçerli parametre değerlerini belirtmek için kullanılır. Önkoşullarda bahsedilen tüm üyeler en az yöntemin kendisi kadar erişilebilir olmalıdır; aksi takdirde, ön koşul bir yöntemin tüm çağıranları tarafından anlaşılamayabilir. Koşulun yan etkileri olmamalıdır. Başarısız önkoşulların çalışma zamanı davranışı çalışma zamanı çözümleyicisi tarafından belirlenir.
Örneğin, aşağıdaki önkoşul parametrenin x
null olmayan olması gerektiğini ifade eder.
Contract.Requires(x != null);
Kodunuzun önkoşul hatasında belirli bir özel durum oluşturması gerekiyorsa, genel aşırı yüklemesini Requires aşağıdaki gibi kullanabilirsiniz.
Contract.Requires<ArgumentNullException>(x != null, "x");
Eski Gerekli Deyimler
Çoğu kod, kod biçiminde bazı parametre doğrulamaları if
--then
throw
içerir. Sözleşme araçları aşağıdaki durumlarda bu deyimleri önkoşul olarak tanır:
deyimleri, bir yöntemdeki diğer deyimlerden önce görünür.
Bu tür deyimler kümesinin tamamı, , , veya yöntemine Requiresyapılan çağrı gibi açık Contract bir yöntem çağrısı tarafından takip edilir.EndContractBlockEnsuresOnThrowEnsures
if
--then
throw
Bu formda deyimler göründüğünde, araçlar bunları eski requires
deyimler olarak tanır. Diziyi if
--then
throw
izleyen başka bir sözleşme yoksa, kodu yöntemiyle sonlandırın.Contract.EndContractBlock
if (x == null) throw new ...
Contract.EndContractBlock(); // All previous "if" checks are preconditions
Önceki testteki koşulun olumsuzlanmış bir önkoşul olduğunu unutmayın. (Gerçek önkoşul x != null
olacaktır.) Olumsuz koşul yüksek oranda kısıtlanmıştır: Önceki örnekte gösterildiği gibi yazılmalıdır; yani yan tümce içermemelidir else
ve yan tümcenin then
gövdesi tek throw
bir deyim olmalıdır. Test if
hem saflık hem de görünürlük kurallarına tabidir (bkz . Kullanım Yönergeleri), ancak throw
ifade yalnızca saflık kurallarına tabidir. Ancak, oluşan özel durumun türü, sözleşmenin gerçekleştiği yöntem kadar görünür olmalıdır.
Postconditions
Son koşul, bir yöntemin sonlandırıldığı durum için sözleşmelerdir. Son koşul, bir yöntemden çıkmadan hemen önce denetleniyor. Başarısız son koşullarının çalışma zamanı davranışı çalışma zamanı çözümleyicisi tarafından belirlenir.
Önkoşullardan farklı olarak, son koşul daha az görünürlüğe sahip üyelere başvurabilir. İstemci, özel durumu kullanarak bir son koşul tarafından ifade edilen bazı bilgileri anlayamaz veya kullanamayabilir, ancak bu, istemcinin yöntemi doğru kullanma becerisini etkilemez.
Standart Son Koşul
yöntemini kullanarak Ensures standart son koşullarını ifade edebilirsiniz. Son koşullar, yöntemin normal şekilde sonlandırılması durumunda olması true
gereken bir koşulu ifade eder.
Contract.Ensures(this.F > 0);
Olağanüstü Son Koşul
Olağanüstü son koşul, bir yöntem tarafından belirli bir özel durum oluştuğunda olması true
gereken son koşuldur. Aşağıdaki örnekte gösterildiği gibi yöntemini kullanarak Contract.EnsuresOnThrow bu son koşulları belirtebilirsiniz.
Contract.EnsuresOnThrow<T>(this.F > 0);
bağımsız değişkeni, alt türü T
olan bir özel durum oluşturulduğunda olması true
gereken koşuldur.
Olağanüstü bir son koşulda kullanılması zor olan bazı özel durum türleri vardır. Örneğin, için türünü Exception kullanmak, T
yığın taşması veya başka bir denetlenmeyen özel durum olsa bile, oluşan özel durumun türünden bağımsız olarak koşulu garanti etmek için yöntemini gerektirir. Olağanüstü son koşullarını yalnızca bir üye çağrıldığında, örneğin bir yöntem çağrısı için atıldığında InvalidTimeZoneException oluşturulabilecek belirli özel durumlar için TimeZoneInfo kullanmanız gerekir.
Özel Son Koşul
Aşağıdaki yöntemler yalnızca son koşullarda kullanılabilir:
yönteminin dönüş türüyle değiştirildiği
T
ifadesiniContract.Result<T>()
kullanarak son koşullarda yöntem dönüş değerlerine başvurabilirsiniz. Derleyici türü çıkaramadığında açıkça sağlamanız gerekir. Örneğin, C# derleyicisi herhangi bir bağımsız değişken almayan yöntemlerin türlerini çıkaramaz, bu nedenle aşağıdaki son koşulu gerektirir:Contract.Ensures(0 <Contract.Result<int>())
Dönüş türünevoid
sahip yöntemler, son koşullarında başvuramazContract.Result<T>()
.Bir son koşuldaki durum öncesi değeri, bir yöntemin veya özelliğin başındaki ifadenin değerini ifade eder. ifadesini
Contract.OldValue<T>(e)
kullanır; buradaT
türüdüre
. Derleyici türünü çıkarabildiğinde genel tür bağımsız değişkenini atlayabilirsiniz. (Örneğin, C# derleyicisi bir bağımsız değişken aldığından türü her zaman çıkarır.) içinde neler olabileceğinee
ve eski bir ifadenin görünebileceği bağlamlara ilişkin çeşitli kısıtlamalar vardır. Eski bir ifade başka bir eski ifade içeremez. En önemlisi, eski bir ifade yöntemin önkoşul durumunda var olan bir değere başvurmalıdır. Başka bir deyişle, yöntemin önkoşulutrue
olduğu sürece değerlendirilebilecek bir ifade olmalıdır. Bu kuralın birkaç örneği aşağıdadır.Değerin yöntemin önkoşul durumunda mevcut olması gerekir. Bir nesnedeki bir alana başvurmak için önkoşullar nesnenin her zaman null olmadığını garanti etmelidir.
Yöntemin eski ifadedeki dönüş değerine başvuramazsınız:
Contract.OldValue(Contract.Result<int>() + x) // ERROR
Eski bir ifadedeki parametrelere
out
başvuramazsınız.Niceleyicinin aralığı yöntemin dönüş değerine bağlıysa, eski bir ifade niceleyicinin bağlı değişkenine bağımlı olamaz:
Contract.ForAll(0, Contract.Result<int>(), i => Contract.OldValue(xs[i]) > 3); // ERROR
Eski bir ifade, bir ForAllExists yöntem çağrısı için dizin oluşturucu veya bağımsız değişken olarak kullanılmadığı sürece veya çağrısındaki anonim temsilcinin parametresine başvuramaz:
Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK Contract.ForAll(0, xs.Length, i => Contract.OldValue(i) > 3); // ERROR
Eski ifadenin değeri anonim temsilcinin parametrelerinden herhangi birine bağlıysa, anonim temsilci veya Exists yöntemi için bir bağımsız değişken olmadığı sürece, anonim bir temsilcinin gövdesinde ForAll eski bir ifade gerçekleşemez:
Method(... (T t) => Contract.OldValue(... t ...) ...); // ERROR
Out
parametreleri bir sorun oluşturur çünkü anlaşmalar yöntemin gövdesinden önce görünür ve çoğu derleyici son koşullarda parametrelere başvurularaout
izin vermez. Bu sorunu çözmek için sınıfı, Contract bir parametreye ValueAtReturn dayalıout
bir son koşula izin veren yöntemini sağlar.public void OutParam(out int x) { Contract.Ensures(Contract.ValueAtReturn(out x) == 3); x = 3; }
yönteminde OldValue olduğu gibi, derleyici türünü çıkarabildiğinde genel tür parametresini atlayabilirsiniz. Sözleşme yeniden yazma yöntemi çağrısı parametresinin
out
değeriyle değiştirir. ValueAtReturn yöntemi yalnızca son koşullarda görünebilir. yönteminin bağımsız değişkeni birout
parametre veya yapıout
parametresinin alanı olmalıdır. İkincisi, yapı oluşturucusunun son koşulundaki alanlara başvururken de yararlıdır.Not
Şu anda kod sözleşmesi çözümleme araçları parametrelerin düzgün başlatılıp başlatılmadığını
out
denetlemez ve son koşulda bahsetmelerini göz ardı eder. Bu nedenle, önceki örnekte, sözleşmeden sonraki satır ona bir tamsayı atamak yerine değerinix
kullansaydı, derleyici doğru hatayı vermezdi. Ancak, CONTRACTS_FULL ön işlemci simgesinin tanımlanmadığı bir derlemede (örneğin, yayın derlemesi), derleyici bir hata döndürür.
Invariants
Nesne sabitleri, bir sınıfın her örneği için nesne bir istemciye her görünür olduğunda doğru olması gereken koşullardır. Nesnenin doğru olarak kabul edildiği koşulları ifade eder.
Sabit yöntemler özniteliğiyle ContractInvariantMethodAttribute işaretlenerek tanımlanır. Sabit yöntemler, aşağıdaki örnekte gösterildiği gibi yöntemine Invariant yapılan çağrı dizisi dışında kod içermemelidir. Bunların her biri tek bir sabit değer belirtir.
[ContractInvariantMethod]
protected void ObjectInvariant ()
{
Contract.Invariant(this.y >= 0);
Contract.Invariant(this.x > this.y);
...
}
Sabit değerler, CONTRACTS_FULL ön işlemci simgesi tarafından koşullu olarak tanımlanır. Çalışma zamanı denetimi sırasında, sabitler her ortak yöntemin sonunda denetleniyor. Sabit bir aynı sınıftaki bir ortak yöntemden bahsederse, normalde bu ortak yöntemin sonunda gerçekleşecek sabit denetim devre dışı bırakılır. Bunun yerine, denetim yalnızca bu sınıfa en dıştaki yöntem çağrısının sonunda gerçekleşir. Başka bir sınıftaki bir yönteme yapılan çağrı nedeniyle sınıf yeniden girilirse de bu durum ortaya çıkar. Sabitler, nesne sonlandırıcısı ve IDisposable.Dispose uygulama için denetlenmiyor.
Kullanım Yönergeleri
Sözleşme Siparişi
Aşağıdaki tabloda, yöntem sözleşmeleri yazarken kullanmanız gereken öğelerin sırası gösterilmektedir.
If-then-throw statements |
Geriye dönük uyumlu genel önkoşullar |
---|---|
Requires | Tüm genel önkoşullar. |
Ensures | Tüm genel (normal) son koşul. |
EnsuresOnThrow | Tüm genel istisnai son koşul. |
Ensures | Tüm özel/iç (normal) son koşul. |
EnsuresOnThrow | Tüm özel/dahili olağanüstü son koşul. |
EndContractBlock | Stil önkoşullarını başka sözleşmeler olmadan kullanıyorsanızif --then throw , denetimler önkoşulsa önceki tüm denetimlerin ön koşul olduğunu belirtmek için EndContractBlock çağrısı yapın. |
Saf -lık
Bir sözleşme içinde çağrılan tüm yöntemler saf olmalıdır; başka bir ifadeyle, önceden var olan herhangi bir durumu güncelleştirmemeleri gerekir. Saf yöntemin, saf yönteme girdikten sonra oluşturulan nesneleri değiştirmesine izin verilir.
Kod sözleşmesi araçları şu anda aşağıdaki kod öğelerinin saf olduğunu varsayar:
ile PureAttributeişaretlenmiş yöntemler.
ile PureAttribute işaretlenmiş türler (öznitelik, türün tüm yöntemleri için geçerlidir).
Özellik alma erişimcileri.
İşleçler (adları "op" ile başlayan ve bir veya iki parametresi ve geçersiz olmayan dönüş türü olan statik yöntemler).
Tam adı "System.Diagnostics.Contracts.Contract", "System.String", "System.IO.Path" veya "System.Type" ile başlayan herhangi bir yöntem.
Temsilci türünün kendisiyle ilişkilendirildiğinden PureAttribute, çağrılan tüm temsilciler. Temsilci türleri System.Predicate<T> ve System.Comparison<T> saf kabul edilir.
Görünürlük
Bir sözleşmede bahsedilen tüm üyeler en azından göründükleri yöntem kadar görünür olmalıdır. Örneğin, özel bir alandan genel bir yöntemin önkoşulunda bahsedilemez; istemcileri yöntemi çağırmadan önce böyle bir sözleşmeyi doğrulayamaz. Ancak, alan ile ContractPublicPropertyNameAttributeişaretlenmişse, bu kurallardan muaftır.
Örnek
Aşağıdaki örnekte kod sözleşmelerinin kullanımı gösterilmektedir.
#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