Kod sözleşmeleri (.NET Framework)

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--thenthrow içerir. Sözleşme araçları aşağıdaki durumlarda bu deyimleri önkoşul olarak tanır:

if--thenthrow Bu formda deyimler göründüğünde, araçlar bunları eski requires deyimler olarak tanır. Diziyi if--thenthrow 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 != nullolacaktı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 ifadesini Contract.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üne void sahip yöntemler, son koşullarında başvuramaz Contract.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; burada T türüdür e. 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ğine e 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şulu trueolduğ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şvurulara out 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 bir out 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ğerini x 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--thenthrow, 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