Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Megjegyzés:
Ez a cikk a .NET-keretrendszerre vonatkozik. Ez nem vonatkozik a .NET újabb implementációira, beleértve a .NET 6-os és újabb verzióit.
A kódszerződések lehetővé teszik az előfeltételek, a posztkondíciók és az objektum-invariánsok megadását a .NET-keretrendszer kódjában. Az előfeltételek olyan követelmények, amelyeket meg kell felelni egy metódus vagy tulajdonság megadásakor. A postconditions az elvárásokat írja le a metódus vagy a tulajdonságkód kilépésének időpontjában. Az objektum-invariánsok egy jó állapotú osztály várt állapotát írják le.
Megjegyzés:
A kódszerződések nem támogatottak a .NET 5+-ban (beleértve a .NET Core-verziókat is). Fontolja meg a null értékű referenciatípusok használatát.
A kódszerződések tartalmazzák a kód megjelölésére szolgáló osztályokat, a fordítási idő elemzéséhez használt statikus elemzőt és egy futtatókörnyezet-elemzőt. A kódszerződések osztályai a System.Diagnostics.Contracts névtérben találhatók.
A kódszerződések előnyei a következők:
Továbbfejlesztett tesztelés: A kódszerződések statikus szerződés-ellenőrzést, futtatókörnyezet-ellenőrzést és dokumentáció-létrehozást biztosítanak.
Automatikus tesztelési eszközök: Kódszerződések használatával értelmesebb egységteszteket hozhat létre az előfeltételeknek nem megfelelő értelmetlen tesztargumentumok kiszűrésével.
Statikus ellenőrzés: A statikus ellenőrző el tudja dönteni, hogy vannak-e szerződésszegések a program futtatása nélkül. Implicit szerződéseket, például null hareferenseket, tömbkorlátokat és explicit szerződéseket keres.
Referenciadokumentáció: A dokumentációgenerátor kibővíti a meglévő XML-dokumentációs fájlokat szerződésadatokkal. Vannak olyan stíluslapok is, amelyek a Sandcastle használatával használhatók, így a létrehozott dokumentációs lapok szerződésszakaszokkal rendelkeznek.
A .NET-keretrendszer összes nyelve azonnal kihasználhatja a szerződések előnyeit; nem kell speciális elemzőt vagy fordítót írnia. A Visual Studio bővítmény lehetővé teszi, hogy meghatározza a végrehajtandó kódszerződés-elemzés szintjét. Az elemzők meggyőződhetnek arról, hogy a szerződések jól formázottak (típusellenőrzés és névfeloldás), és a szerződések lefordított formáját hozhatják létre közös köztes nyelven (CIL) formátumban. A Visual Studióban történő szerződések készítésével kihasználhatja az eszköz által biztosított standard IntelliSense előnyeit.
A szerződésosztály legtöbb metódusa feltételesen van lefordítva; vagyis a fordító csak akkor bocsát ki hívásokat ezekre a módszerekre, ha egy speciális szimbólumot határoz meg, CONTRACTS_FULL az #define irányelv használatával. CONTRACTS_FULL lehetővé teszi a szerződések írását a kódban irányelvek használata #ifdef nélkül; létrehozhat különböző buildeket, néhányat szerződésekkel, mások nélkül.
A kódszerződések használatára vonatkozó eszközöket és részletes utasításokat a Visual Studio piactéri webhelyén található Kódszerződések című témakörben találja.
Előfeltételek
A metódussal Contract.Requires előfeltételeket fejezhet ki. Az előfeltételek egy metódus meghívásának állapotát határozzák meg. Ezek általában érvényes paraméterértékek megadására szolgálnak. Az előfeltételekben említett tagoknak legalább olyan hozzáférhetőnek kell lenniük, mint maga a módszer; ellenkező esetben előfordulhat, hogy az előfeltételt nem minden hívó értelmezi. A feltételnek nem lehet mellékhatása. A sikertelen előfeltételek futásidejű viselkedését a futtatókörnyezet-elemző határozza meg.
Az alábbi előfeltétel például azt fejezi ki, hogy a paraméternek x nem null értékűnek kell lennie.
Contract.Requires(x != null);
Ha a kódnak egy előfeltétel meghibásodása esetén egy meghatározott kivételt kell dobnia, használhatja a generikus túlterhelést Requires az alábbiak szerint.
Contract.Requires<ArgumentNullException>(x != null, "x");
Az örökségi kerethez nyilatkozatok szükségesek
A legtöbb kód tartalmaz valamilyen paraméterérvényesítést if-then-throw kód formájában. A szerződéses eszközök ezeket az utasításokat előfeltételekként ismerik fel a következő esetekben:
Az utasítások a metódus többi utasítása előtt jelennek meg.
Az ilyen utasítások teljes készletét explicit Contract metódushívás követi, például a Requires, Ensures, EnsuresOnThrowvagy EndContractBlock metódus hívása.
Amikor if-then-throw az utasítások megjelennek ebben az formában, az eszközök régi requires utasításokként ismerik fel őket. Ha más szerződések nem követik a if-then-throw sorrendet, fejezd be a kódot a Contract.EndContractBlock metódussal.
if (x == null) throw new ...
Contract.EndContractBlock(); // All previous "if" checks are preconditions
Vegye figyelembe, hogy az előző tesztben szereplő feltétel egy negált előfeltétel. (A tényleges előfeltétel x != null.) A tagadott előfeltétel szigorúan korlátozott: az előző példában látható módon kell megírni; azaz nem tartalmazhat else záradékokat, és a then záradék törzsének egyetlen throw utasításnak kell lennie. A if tesztre mind a tisztasági, mind a láthatósági szabályok vonatkoznak (lásd a használati útmutatót), de a throw kifejezésre csak tisztasági szabályok vonatkoznak. A kidobott kivétel típusának azonban ugyanolyan láthatónak kell lennie, mint a szerződés előfordulási módja.
Utókondíciók
A postfeltételek olyan szerződések, amelyek a metódus állapotára vonatkoznak, amikor az befejeződik. A rendszer a metódus elhagyása előtt ellenőrzi az utókondíciót. A sikertelen utókondíciók futásidejű viselkedését a futtatókörnyezet-elemző határozza meg.
Az előfeltételektől eltérően a posztkondíciók kevésbé ismert tagokra hivatkozhatnak. Előfordulhat, hogy az ügyfél nem tudja megérteni vagy felhasználni a privát állapotot használó utókondíció által kifejezett információk egy részét, de ez nem befolyásolja az ügyfél azon képességét, hogy helyesen használja a módszert.
Szokványos Utófeltételek
A Ensures módszer használatával szabványos utókondicionálásokat fejezhet ki. A posztkondíciók olyan feltételt fejeznek ki, amelyet a módszer normál leállásakor kell megadni true .
Contract.Ensures(this.F > 0);
Kivételes utókondíciók
A kivételes posztkondíciók olyan posztkondíciók, amelyeknek akkor kell lenniük true , ha egy adott kivételt egy metódus alkalmaz. Ezeket az utókondicionálásokat a Contract.EnsuresOnThrow metódussal adhatja meg, ahogy az alábbi példa is mutatja.
Contract.EnsuresOnThrow<T>(this.F > 0);
Az argumentum az a feltétel, amelynek akkor kell lennie true, amikor egy kivételt dobnak, ami egy altípusa T.
Vannak olyan kivételtípusok, amelyeket nehéz használni egy kivételes utókondícióban. Például, amikor a Exception típusra T hivatkozik, a metódusnak a feltételt garantálnia kell a kidobott kivétel típusától függetlenül, még akkor is, ha az egy verem túlcsordulás vagy más, nem kontrollálható kivétel. Kivételes utókondíciókat csak olyan speciális kivételek kezelésekor használjunk, amelyek egy tag meghívásakor fordulhatnak elő, például amikor egy InvalidTimeZoneException keletkezik TimeZoneInfo metódushíváskor.
Speciális utókondíciók
A következő módszerek csak utókondíciókon belül használhatók:
Az utókondíciókban a metódus visszatérési értékeire a kifejezéssel
Contract.Result<T>()hivatkozhat, aholTa metódus visszatérési típusa váltja fel. Ha a fordító nem tudja kikövetkeztetni a típust, explicit módon kell megadnia. A C#-fordító például nem tud olyan metódustípusokat kikövetkeztetni, amelyek nem vesznek fel argumentumokat, ezért a következő utókondicionálást igényli:Contract.Ensures(0 <Contract.Result<int>())A visszatérésivoidtípusú metódusok nem hivatkozhatnakContract.Result<T>()az utókondicionálásokban.A postcondition prestate értéke egy metódus vagy tulajdonság elején lévő kifejezés értékére utal. A kifejezést
Contract.OldValue<T>(e)használja, aholTaetípusa. Kihagyhatja az általános típusargumentumot, ha a fordító képes a típusára következtetni. (A C#-fordító például mindig a típusra következtet, mert argumentumot vesz igénybe.) Számos korlátozás van arra, hogy mi történhet,eés hogy milyen környezetekben jelenhet meg egy régi kifejezés. A régi kifejezések nem tartalmazhatnak másik régi kifejezést. A legfontosabb, hogy egy régi kifejezésnek olyan értékre kell hivatkoznia, amely a metódus előfeltételeinek állapotában létezett. Más szóval olyan kifejezésnek kell lennie, amely kiértékelhető mindaddig, amíg a metódus előfeltétele .trueÍme, a szabály több példája.Az értéknek a metódus előfeltételeinek állapotában kell lennie. Ahhoz, hogy egy objektumon lévő mezőre hivatkozhasson, az előfeltételeknek garantálniuk kell, hogy az objektum mindig nem null értékű.
Egy régi kifejezésben nem hivatkozhat a metódus visszatérési értékére:
Contract.OldValue(Contract.Result<int>() + x) // ERRORRégi kifejezésben nem hivatkozhat
outparaméterekre.Egy régi kifejezés nem függhet a kvantáló kötött változójától, ha a kvantáló tartománya a metódus visszatérési értékétől függ:
Contract.ForAll(0, Contract.Result<int>(), i => Contract.OldValue(xs[i]) > 3); // ERROREgy régi kifejezés csak akkor hivatkozhat a névtelen meghatalmazott paraméterére egy ForAll hívásban vagy Exists hívásban, ha azt indexelőként vagy argumentumként használják metódushíváshoz:
Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK Contract.ForAll(0, xs.Length, i => Contract.OldValue(i) > 3); // ERRORA névtelen meghatalmazott törzsében nem fordulhat elő régi kifejezés, ha a régi kifejezés értéke a névtelen meghatalmazott egyik paraméterétől függ, kivéve, ha a névtelen meghatalmazott argumentuma a ForAll metódusnak:Exists
Method(... (T t) => Contract.OldValue(... t ...) ...); // ERROROuta paraméterek problémát okoznak, mert a szerződések a metódus törzse előtt jelennek meg, és a legtöbb fordító nem engedélyezi a paraméterekreoutvaló hivatkozást az utókondíciókban. A probléma megoldásához az Contract osztály biztosítja a ValueAtReturn metódust, amely lehetővé teszi, hogy az utókondíció egyoutparaméteren alapuljon.public void OutParam(out int x) { Contract.Ensures(Contract.ValueAtReturn(out x) == 3); x = 3; }A metódushoz OldValue hasonlóan kihagyhatja az általános típusparamétert, amikor a fordító képes a típusára következtetni. A szerződés újraírója a metódushívást a paraméter értékére cseréli
out. A ValueAtReturn metódus csak utókondíciókban jelenhet meg. A metódus argumentumának egyoutparaméternek vagy egy struktúraparaméteroutmezőjének kell lennie. Ez utóbbi akkor is hasznos, ha egy struktúra-konstruktor utófeltételében levő mezőkre hivatkozik.Megjegyzés:
A kódszerződés-elemző eszközök jelenleg nem ellenőrzik, hogy a
outparamétereket megfelelően inicializálták-e, és figyelmen kívül hagyják az utófeltételben való említést. Ezért az előző példában, ha a szerződés utáni sorxértékét használta volna egész szám hozzárendelése helyett, akkor a fordító nem a megfelelő hibát adná ki. Olyan buildek esetében azonban, amelyekben nincs definiálva az CONTRACTS_FULL előfeldolgozó szimbólum (például kiadási build), a fordító hibát fog okozni.
Invariánsok
Az objektum-invariánsok olyan feltételek, amelyeknek igaznak kell lenniük egy osztály minden példányára, amikor az objektum látható az ügyfél számára. Kifejezik azokat a feltételeket, amelyek alapján az objektum helyesnek tekinthető.
Az invariáns metódusokat az ContractInvariantMethodAttribute attribútummal jelöltük meg. Az invariáns metódusoknak nem tartalmazhatnak kódot, kivéve a Invariant metódushoz intézett hívások sorozatát, amelyek mindegyike egy-egy invariánst határoz meg az alábbi példában látható módon.
[ContractInvariantMethod]
protected void ObjectInvariant ()
{
Contract.Invariant(this.y >= 0);
Contract.Invariant(this.x > this.y);
...
}
Az invariánsokat feltételesen az CONTRACTS_FULL előfeldolgozó szimbólum határozza meg. A futásidejű ellenőrzés során a rendszer minden nyilvános módszer végén ellenőrzi a változókat. Ha egy invariáns egy nyilvános metódust említ ugyanabban az osztályban, a nyilvános metódus végén általában előforduló invariáns ellenőrzés le van tiltva. Ehelyett az ellenőrzés csak az adott osztályra irányuló legkülső metódushívás végén történik. Ez akkor is előfordul, ha az osztály újra belép egy másik osztály metódushívása miatt. Az invariánsokat nem ellenőrzik az objektum-véglegesítő és a IDisposable.Dispose megvalósítás esetében.
Használati irányelvek
Szerződés megrendelése
Az alábbi táblázat a metódusszerződések írásakor használandó elemek sorrendjét mutatja be.
If-then-throw statements |
Visszamenőlegesen kompatibilis nyilvános előfeltételek |
|---|---|
| Requires | Minden nyilvános előfeltétel. |
| Ensures | Minden nyilvános (normál) utófeltétel. |
| EnsuresOnThrow | Nyilvános kivételes utókondíciók mind. |
| Ensures | Minden privát/belső (normál) utófeltétel. |
| EnsuresOnThrow | Minden privát/belső kivételes utókondíció. |
| EndContractBlock | Ha if-then-throw stíluselőfeltételeket használ más szerződés nélkül, hívja meg a EndContractBlock-t, hogy jelezze, az összes korábbi ellenőrzés előfeltétel. |
Tisztaság
A szerződésben lehívott összes metódusnak tisztanak kell lennie; vagyis nem frissíthetnek semmilyen már létező állapotot. A tiszta metódusok módosíthatják a tiszta metódusba való belépés után létrehozott objektumokat.
A kódszerződés eszközei jelenleg feltételezik, hogy a következő kódelemek tisztaak:
Azokat a metódusokat, amelyeket a PureAttribute-val jelöltek.
A következővel PureAttribute megjelölt típusok (az attribútum a típus összes metódusára vonatkozik).
A tulajdonsághoz kellékek lekérése.
Operátorok (statikus metódusok, amelyeknek a neve "op" néven kezdődik, és amelyek egy vagy két paraméterrel és nem érvénytelen visszatérési típussal rendelkeznek).
Bármely metódus, amelynek teljes neve "System.Diagnostics.Contracts.Contract", "System.String", "System.IO.Path" vagy "System.Type" néven kezdődik.
Bármely meghívott delegált, feltéve, hogy a delegált típusa maga rendelkezik a PureAttribute attribútummal. A delegált típusok System.Predicate<T> , és System.Comparison<T> tisztanak minősülnek.
Láthatóság
A szerződésben említett összes tagnak legalább olyan láthatónak kell lennie, mint az a módszer, amelyben megjelennek. Nyilvános metódus előfeltételei között például nem szerepelhet magánmező; az ügyfelek nem tudják érvényesíteni az ilyen szerződést, mielőtt meghívják a metódust. Ha azonban a mező a következővel ContractPublicPropertyNameAttributevan megjelölve, akkor mentesül ezek alól a szabályok alól.
példa
Az alábbi példa a kódszerződések használatát mutatja be.
#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