Aracılığıyla paylaş


Kod sözleşmeleri (.NET Framework)

Uyarı

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şullar, yöntem veya özellik kodundan çıkış anındaki beklentileri açıklar. Nesne sabitleri, iyi durumda olan bir sınıf için beklenen durumu açıklar.

Uyarı

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 .

Önkoş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, Requires'nin genel aşırı yükleme fonksiyonunu aşağıdaki gibi kullanabilirsiniz.

Contract.Requires<ArgumentNullException>(x != null, "x");

Miras Gereklilik Beyanları

Ç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:

if - then - throw Bu formda deyimler göründüğünde, araçlar bunları eski requires deyimler olarak tanır. if - then - throw dizisini izleyen başka bir sözleşme yoksa, kodu Contract.EndContractBlock yöntemiyle sonlandırın.

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 önkoşul oldukça kısıtlıdır: Önceki örnekte gösterildiği gibi yazılmalıdır; yani, else içermemelidir ve then yan tümcesinin gövdesi bir throw ifadesi 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.

Son Koşullar

Son koşullar, bir yöntemin tamamlandığındaki 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.

Ön koşullardan farklı olarak, son koşullar 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şullar

Olağanüstü son koşullar, bir yöntem belirli bir özel durum fırlattığında söz konusu son koşullar true olmalıdır. 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şken, alt türü true olan bir özel durum fırlatıldığında T olması gereken koşuldur.

Olağanüstü bir son koşulda kullanılması zor olan bazı özel durum türleri vardır. Örneğin, türü Exception olan T kullanmak, yığın taşması veya başka bir kontrol edilemeyen özel durum olsa bile, fırlatılan özel durumun türünden bağımsız olarak koşulun garanti edilmesini yöntemin sağlamasını gerektirir. Belirli istisnai durumlar için, örneğin bir InvalidTimeZoneException yöntemi çağrıldığında bir TimeZoneInfo atıldığında, yalnızca istisnai son koşulları kullanmanız gerekir.

Özel Son Koşul

Aşağıdaki yöntemler yalnızca ardıl koşullarda kullanılabilir:

  • Son koşullarda yöntem dönüş değerlerine, yönteminin dönüş türüyle değiştirilen Contract.Result<T>() ifadesini T kullanarak başvurabilirsiniz. Derleyici türü çıkaramadığında türü açıkça belirtmeniz 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, Contract.Result<T>()'ye son koşullarında başvuramaz.

  • Bir son durumdaki önceki durum değeri, bir yöntemin veya özelliğin başındaki ifadenin değerini ifade eder. Contract.OldValue<T>(e) ifadesini kullanır; burada T, e türündedir. Derleyici türünü çıkarabildiğinde genel tür bağımsız değişkenini kullanmayabilirsiniz. Örneğin, C# derleyicisi bir bağımsız değişken aldığı için türü her zaman belirler. e içinde neler meydana gelebileceği ve eski bir ifadenin görünebileceği bağlamlar üzerinde ç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
      
    • Anonim bir temsilcinin parametresine, dizin oluşturucu veya bir yöntem çağrısının bağımsız değişkeni olarak kullanılmadığı sürece eski bir ifade 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 ve anonim temsilci ForAll veya Exists yöntemine bir bağımsız değişken olarak sunulmuyorsa, anonim bir temsilcinin gövdesinde 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 Contract sınıfı, ValueAtReturn yöntemini sunar, bu yöntem out parametresine dayalı bir son koşul oluşturulmasına olanak tanır.

      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 yazıcı, yöntem çağrısını out parametresinin değeriyle değiştirir. ValueAtReturn yöntemi yalnızca son koşullarda görünebilir. Yöntemin bağımsız değişkeni bir out parametre veya bir yapı out parametresinin alanı olmalıdır. İkincisi, bir yapı oluşturucusunun son durumu içindeki alanlara atıfta bulunurken de yararlıdır.

      Uyarı

      Şu anda kod sözleşmesi çözümleme araçları out parametrelerinin düzgün şekilde başlatılıp başlatılmadığını denetlemez ve bunların son koşulda belirtilmelerini göz ardı eder. Bu nedenle, önceki örnekte, sözleşmeden sonraki satır ona bir tamsayı atamak yerine değerini x'dan alsaydı, derleyici gerekli 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.

Değişmezler

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, her genel yöntemin sonunda değişmezler denetleniyor. Bir sabit, aynı sınıftaki bir public yöntemden bahsederse, normalde bu public yöntemin sonunda gerçekleşmesi gereken sabit kontrolü 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öntemin çağrılması nedeniyle sınıfa yeniden girilirse bu durum da ortaya çıkar. Değişmezler, nesne sonlandırıcısı ve IDisposable.Dispose uygulaması 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şullar.
EnsuresOnThrow Tüm genel istisnai sonrası koşullar.
Ensures Tüm özel/iç (normal) son koşullar.
EnsuresOnThrow Tüm özel/dahili istisnai son durumlar.
EndContractBlock Stil önkoşullarını başka sözleşmeler olmadan kullanıyorsanız if-then-throw tarzı 'if' kontrolleri yaparken, tüm önceki 'if' denetimlerinin önkoşul olduğunu belirtmek için EndContractBlock çağrısı yapın.

Saflı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.

  • PureAttribute ile nitelendirilmiş temsilci türü kendisi olduğu sürece, çağrılan herhangi bir temsilci. 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