程式碼合約Code Contracts

程式碼合約可讓您指定程式碼中的前置條件、後置條件和物件非變異值。Code contracts provide a way to specify preconditions, postconditions, and object invariants in your code. 前置條件是輸入方法或屬性時,必須符合的需求。Preconditions are requirements that must be met when entering a method or property. 後置條件描述在方法或屬性程式碼結束時的期望。Postconditions describe expectations at the time the method or property code exits. 物件非變異值描述針對處於良好狀態的類別,所預期的狀態。Object invariants describe the expected state for a class that is in a good state.

程式碼合約包含用來標示程式碼的類別、用來進行編譯時期分析的靜態分析器,以及執行階段分析器。Code contracts include classes for marking your code, a static analyzer for compile-time analysis, and a runtime analyzer. 您可以在 System.Diagnostics.Contracts 命名空間中找到程式碼合約的類別。The classes for code contracts can be found in the System.Diagnostics.Contracts namespace.

程式碼合約的優點包括:The benefits of code contracts include the following:

  • 改良的測試:程式碼合約提供靜態合約驗證、執行階段檢查,以及文件產生。Improved testing: Code contracts provide static contract verification, runtime checking, and documentation generation.

  • 自動測試工具:您可以使用程式碼合約來篩選掉不符合前置條件的無意義的測試引數,以產生較有意義的單元測試。Automatic testing tools: You can use code contracts to generate more meaningful unit tests by filtering out meaningless test arguments that do not satisfy preconditions.

  • 靜態驗證:靜態檢查工具可以決定是否有任何合約違規,而不需要執行程式。Static verification: The static checker can decide whether there are any contract violations without running the program. 它會檢查隱含的合約,例如 null 取值和陣列界限,以及明確的合約。It checks for implicit contracts, such as null dereferences and array bounds, and explicit contracts.

  • 參考文件:文件產生器會使用合約資訊來增強現有的 XML 文件檔案。Reference documentation: The documentation generator augments existing XML documentation files with contract information. 此外,還有一些可搭配 Sandcastle 使用的樣式表,能夠讓產生的文件頁面具有合約區段。There are also style sheets that can be used with Sandcastle so that the generated documentation pages have contract sections.

所有 .NET Framework 語言都可以立即利用合約;您不必撰寫特殊的剖析器或編譯器。All .NET Framework languages can immediately take advantage of contracts; you do not have to write a special parser or compiler. Visual Studio 增益集可讓您指定所要執行之程式碼合約分析的層級。A Visual Studio add-in lets you specify the level of code contract analysis to be performed. 分析器可以確認合約語式正確 (類型檢查和名稱解析),並且可以產生 Microsoft 中繼語言 (MSIL) 格式合約的編譯形式。The analyzers can confirm that the contracts are well-formed (type checking and name resolution) and can produce a compiled form of the contracts in Microsoft intermediate language (MSIL) format. 在 Visual Studio 中撰寫合約,可讓您利用該工具所提供的標準 IntelliSense。Authoring contracts in Visual Studio lets you take advantage of the standard IntelliSense provided by the tool.

合約類別中的大部分方法都是有條件地編譯;也就是說,唯有當您使用 #define 指示詞,來定義特殊符號 CONTRACTS_FULL 時,編譯器才會發出呼叫至這些方法。Most methods in the contract class are conditionally compiled; that is, the compiler emits calls to these methods only when you define a special symbol, CONTRACTS_FULL, by using the #define directive. CONTRACTS_FULL 可讓您在程式碼中撰寫合約,而不需使用 #ifdef 指示詞;您可以產生不同的組建,有些有合約,有些則沒有。CONTRACTS_FULL lets you write contracts in your code without using #ifdef directives; you can produce different builds, some with contracts, and some without.

如需使用程式碼合約的工具和詳細指示,請參閱 Visual Studio marketplace 網站上的程式代碼合約For tools and detailed instructions for using code contracts, see Code Contracts on the Visual Studio marketplace site.

前置條件Preconditions

您可以使用 Contract.Requires 方法來表示前置條件。You can express preconditions by using the Contract.Requires method. 前置條件可指定叫用方法時的狀態。Preconditions specify state when a method is invoked. 其通常是用來指定有效的參數值。They are generally used to specify valid parameter values. 前置條件中提及之所有成員的可存取性,必須至少與方法本身相同,否則可能無法讓方法的所有呼叫端都了解該前置條件。All members that are mentioned in preconditions must be at least as accessible as the method itself; otherwise, the precondition might not be understood by all callers of a method. 該條件必須沒有副作用。The condition must have no side-effects. 失敗前置條件的執行階段行為,取決於執行階段分析器。The run-time behavior of failed preconditions is determined by the runtime analyzer.

例如,下列前置條件表示 x 參數必須為非 null。For example, the following precondition expresses that parameter x must be non-null.

Contract.Requires(x != null);

如果您的程式碼必須在前置條件失敗時擲回特定例外狀況,您可以使用 Requires 的泛型多載,如下所示。If your code must throw a particular exception on failure of a precondition, you can use the generic overload of Requires as follows.

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

舊版需要陳述式Legacy Requires Statements

大部分程式碼都包含 if-then-throw 程式碼形式的一些參數驗證。Most code contains some parameter validation in the form of if-then-throw code. 在下列情況下,合約工具會將這些陳述式辨識為前置條件:The contract tools recognize these statements as preconditions in the following cases:

if-then-throw 陳述式以這種形式出現時,工具會將其識別為舊版 requires 陳述式。When if-then-throw statements appear in this form, the tools recognize them as legacy requires statements. 如果 if-then-throw 序列後面沒有接著任何其他合約,則以 Contract.EndContractBlock 方法來結束程式碼。If no other contracts follow the if-then-throw sequence, end the code with the Contract.EndContractBlock method.

if (x == null) throw new ...
Contract.EndContractBlock(); // All previous "if" checks are preconditions

請注意,先前測試中的條件是否定的前置條件。Note that the condition in the preceding test is a negated precondition. (實際的前置條件會 x != null)。否定的前置條件具有高度限制:必須如前一個範例所示加以撰寫。也就是說,它應該不會包含 else 子句,而 then 子句的主體必須是單一 throw 語句。(The actual precondition would be x != null.) A negated precondition is highly restricted: It must be written as shown in the previous example; that is, it should contain no else clauses, and the body of the then clause must be a single throw statement. if 測試受限於單純性和可視性規則 (請參閱使用方式方針),但是 throw 運算式只受限於單純性規則。The if test is subject to both purity and visibility rules (see Usage Guidelines), but the throw expression is subject only to purity rules. 不過,所擲回之例外狀況的類型可視性,必須與發生合約的方法相同。However, the type of the exception thrown must be as visible as the method in which the contract occurs.

PostconditionsPostconditions

後置條件是當方法終止時的方法狀態合約。Postconditions are contracts for the state of a method when it terminates. 在方法臨結束前,才會檢查後置條件。The postcondition is checked just before exiting a method. 失敗後置條件的執行階段行為,取決於執行階段分析器。The run-time behavior of failed postconditions is determined by the runtime analyzer.

不同於前置條件,後置條件可能會參考可視性較低的成員。Unlike preconditions, postconditions may reference members with less visibility. 用戶端可能無法使用私用狀態來了解或利用以後置條件來表示的一些資訊,但這不影響用戶端正確地使用方法的能力。A client may not be able to understand or make use of some of the information expressed by a postcondition using private state, but this does not affect the client's ability to use the method correctly.

標準後置條件Standard Postconditions

您可以使用 Ensures 方法來表示後置條件。You can express standard postconditions by using the Ensures method. 後置條件會表示方法正常終止時,必須為 true 的條件。Postconditions express a condition that must be true upon normal termination of the method.

Contract.Ensures(this.F > 0);

例外後置條件Exceptional Postconditions

例外後置條件是當方法擲回特定例外狀況時,應為 true 的後置條件。Exceptional postconditions are postconditions that should be true when a particular exception is thrown by a method. 您可以使用 Contract.EnsuresOnThrow 方法來指定這些後置條件,如下列範例所示。You can specify these postconditions by using the Contract.EnsuresOnThrow method, as the following example shows.

Contract.EnsuresOnThrow<T>(this.F > 0);

此引數是每當擲回的例外狀況是 T 的子類型時,必須為 true 的條件。The argument is the condition that must be true whenever an exception that is a subtype of T is thrown.

有些例外狀況類型很難用在例外後置條件中。There are some exception types that are difficult to use in an exceptional postcondition. 例如,將類型 Exception 用於 T 時,無論擲回的例外狀況類型為何,即使是堆疊溢位或其他無法控制的例外狀況,該方法都必須保證此條件。For example, using the type Exception for T requires the method to guarantee the condition regardless of the type of exception that is thrown, even if it is a stack overflow or other impossible-to-control exception. 例外後置條件應該只限用於呼叫成員時,可能會擲回的特定例外狀況;例如,針對 TimeZoneInfo 方法呼叫擲回 InvalidTimeZoneException 時。You should use exceptional postconditions only for specific exceptions that might be thrown when a member is called, for example, when an InvalidTimeZoneException is thrown for a TimeZoneInfo method call.

特殊後置條件Special Postconditions

下列方法僅限用於後置條件中:The following methods may be used only within postconditions:

  • 若要參考後置條件中的方法傳回值,您可以使用 Contract.Result<T>() 運算式,其中 T 要取代為方法的傳回類型。You can refer to method return values in postconditions by using the expression Contract.Result<T>(), where T is replaced by the return type of the method. 當編譯器無法推斷類型時,您必須明確地提供類型。When the compiler is unable to infer the type, you must explicitly provide it. 例如,C# 編譯器無法推斷沒有採用任何引數之方法的類型,因此需要下列後置條件:Contract.Ensures(0 <Contract.Result<int>()) 傳回類型為 void 的方法,不能參考其後置條件中的 Contract.Result<T>()For example, the C# compiler is unable to infer types for methods that do not take any arguments, so it requires the following postcondition: Contract.Ensures(0 <Contract.Result<int>()) Methods with a return type of void cannot refer to Contract.Result<T>() in their postconditions.

  • 後置條件中的 prestate 值是指位於方法或屬性開頭之運算式的值。A prestate value in a postcondition refers to the value of an expression at the start of a method or property. 它會使用 Contract.OldValue<T>(e) 運算式,其中 Te 的類型。It uses the expression Contract.OldValue<T>(e), where T is the type of e. 只要編譯器能夠推斷其類型,您就可以省略泛型類型引數。You can omit the generic type argument whenever the compiler is able to infer its type. (例如, C#編譯器一律會推斷類型,因為它接受引數)。e 和可能出現舊運算式的內容有幾項限制。(For example, the C# compiler always infers the type because it takes an argument.) There are several restrictions on what can occur in e and the contexts in which an old expression may appear. 舊運算式不能包含另一個舊運算式。An old expression cannot contain another old expression. 最重要的是,舊的運算式必須參考存在於方法前置條件狀態中的值。Most importantly, an old expression must refer to a value that existed in the method's precondition state. 換句話說,只要方法的前置條件是 true,它就必須是可供評估的運算式。In other words, it must be an expression that can be evaluated as long as the method's precondition is true. 以下是該規則的一些執行個體。Here are several instances of that rule.

    • 該值必須存在於方法的前置條件狀態中。The value must exist in the method's precondition state. 為了參考物件上的欄位,前置條件必須保證物件一律為非 null。In order to reference a field on an object, the preconditions must guarantee that the object is always non-null.

    • 您不能在舊運算式中參考方法的傳回值:You cannot refer to the method's return value in an old expression:

      Contract.OldValue(Contract.Result<int>() + x) // ERROR
      
    • 您不能在舊運算式中參考 out 參數。You cannot refer to out parameters in an old expression.

    • 如果數量詞的範圍取決於方法的傳回值,則舊運算式不能取決於數量詞的繫結變數中:An old expression cannot depend on the bound variable of a quantifier if the range of the quantifier depends on the return value of the method:

      Contract.ForAll(0, Contract.Result<int>(), i => Contract.OldValue(xs[i]) > 3); // ERROR
      
    • 舊運算式不能參考 ForAllExists 呼叫中匿名委派的參數,除非是用來做為方法呼叫的索引子或引數:An old expression cannot refer to the parameter of the anonymous delegate in a ForAll or Exists call unless it is used as an indexer or argument to a method call:

      Contract.ForAll(0, xs.Length, i => Contract.OldValue(xs[i]) > 3); // OK
      Contract.ForAll(0, xs.Length, i => Contract.OldValue(i) > 3); // ERROR
      
    • 如果舊運算式的值取決於匿名委派的任何參數,則舊運算式不能出現在匿名委派的主體中,除非匿名委派是 ForAllExists 方法的引數:An old expression cannot occur in the body of an anonymous delegate if the value of the old expression depends on any of the parameters of the anonymous delegate, unless the anonymous delegate is an argument to the ForAll or Exists method:

      Method(... (T t) => Contract.OldValue(... t ...) ...); // ERROR
      
    • Out 參數出現問題,因為合約出現在方法的主體之前,而大部分編譯器不允許參考後置條件中的 out 參數。Out parameters present a problem because contracts appear before the body of the method, and most compilers do not allow references to out parameters in postconditions. 為了解決這個問題,Contract 類別提供了 ValueAtReturn 方法,可允許以 out 參數為基礎的後置條件。To solve this problem, the Contract class provides the ValueAtReturn method, which allows a postcondition based on an out parameter.

      public void OutParam(out int x)
      {
          Contract.Ensures(Contract.ValueAtReturn(out x) == 3);
          x = 3;
      }
      

      如同 OldValue 方法,只要編譯器能夠推斷其類型,您就可以省略泛型型別引數。As with the OldValue method, you can omit the generic type parameter whenever the compiler is able to infer its type. 合約重寫器會將方法呼叫取代為 out 參數的值。The contract rewriter replaces the method call with the value of the out parameter. ValueAtReturn 方法只會出現在後置條件中。The ValueAtReturn method may appear only in postconditions. 方法的引數必須是 out 參數,或是結構 out 參數的欄位。The argument to the method must be an out parameter or a field of a structure out parameter. 參考結構建構函式後置條件中的欄位時,後者也很有用。The latter is also useful when referring to fields in the postcondition of a structure constructor.

      注意

      目前,程式碼合約分析工具不會檢查 out 參數是否正確初始化,並忽略其於後置條件中的相關記錄。Currently, the code contract analysis tools do not check whether out parameters are initialized properly and disregard their mention in the postcondition. 因此,在上述範例中,如果合約之後的字行使用 x 的值,而不是指派整數給它,則編譯器不會發出正確的錯誤。Therefore, in the previous example, if the line after the contract had used the value of x instead of assigning an integer to it, a compiler would not issue the correct error. 不過,在未定義 CONTRACTS_FULL 前置處理器符號的組建 (例如 asa 發行組建) 上,編譯器會發出錯誤。However, on a build where the CONTRACTS_FULL preprocessor symbol is not defined (such asa release build), the compiler will issue an error.

非變異值Invariants

只要用戶端可以看到該物件,針對類別的每個執行個體,物件非變異值都是應該為 true 的條件。Object invariants are conditions that should be true for each instance of a class whenever that object is visible to a client. 在其表示的條件下,物件會被視為正確。They express the conditions under which the object is considered to be correct.

非變異方法會標示 ContractInvariantMethodAttribute 屬性,以供識別。The invariant methods are identified by being marked with the ContractInvariantMethodAttribute attribute. 非變異方法絕不能包含任何程式碼,但是對 Invariant 方法的一連串呼叫除外,其中每個呼叫都會指定一個個別非變異值,如下列範例所示。The invariant methods must contain no code except for a sequence of calls to the Invariant method, each of which specifies an individual invariant, as shown in the following example.

[ContractInvariantMethod]
protected void ObjectInvariant ()
{
    Contract.Invariant(this.y >= 0);
    Contract.Invariant(this.x > this.y);
    ...
}

CONTRACTS_FULL 前置處理器符號會有條件地定義非變異值。Invariants are conditionally defined by the CONTRACTS_FULL preprocessor symbol. 在執行階段檢查期間,會在每個公用方法的結尾檢查非變異值。During run-time checking, invariants are checked at the end of each public method. 如果非變異值提及在相同類別中的公用方法,則會停用通常在該公用方法結尾進行的非變異檢查。If an invariant mentions a public method in the same class, the invariant check that would normally happen at the end of that public method is disabled. 此檢查反而只會發生在該類別最外層的方法呼叫結尾。Instead, the check occurs only at the end of the outermost method call to that class. 如果因為呼叫另一個類別上的方法而重新輸入此類別,也會進行這項檢查。This also happens if the class is re-entered because of a call to a method on another class. 不會檢查物件完成項和 IDisposable.Dispose 實作為不變數。Invariants are not checked for an object finalizer and an IDisposable.Dispose implementation.

用法方針Usage Guidelines

合約排序Contract Ordering

下表顯示當您撰寫方法合約時,應該使用的項目順序。The following table shows the order of elements you should use when you write method contracts.

If-then-throw statements 回溯相容公用前置條件Backward-compatible public preconditions
Requires 所有公用前置條件。All public preconditions.
Ensures 所有公用 (一般) 後置條件。All public (normal) postconditions.
EnsuresOnThrow 所有公用例外後置條件。All public exceptional postconditions.
Ensures 所有私用/內部 (一般) 後置條件。All private/internal (normal) postconditions.
EnsuresOnThrow 所有私用/內部 (一般) 例外後置條件。All private/internal exceptional postconditions.
EndContractBlock 如果使用 if-then-throw 樣式前置條件,而沒有搭配任何其他合約,則呼叫 EndContractBlock,以指出所有先前的 if 檢查都是前置條件。If using if-then-throw style preconditions without any other contracts, place a call to EndContractBlock to indicate that all previous if checks are preconditions.

單純性Purity

在合約中呼叫的所有方法都必須都是單純的;也就是說,這些方法絕不能更新任何預先存在的狀態。All methods that are called within a contract must be pure; that is, they must not update any preexisting state. 單純方法可以修改進入單純方法之後已建立的物件。A pure method is allowed to modify objects that have been created after entry into the pure method.

程式碼合約工具目前假設下列程式碼項目是單純的:Code contract tools currently assume that the following code elements are pure:

  • 標示 PureAttribute 的方法。Methods that are marked with the PureAttribute.

  • 標示 PureAttribute 的類型 (此屬性會套用至所有類型的方法)。Types that are marked with the PureAttribute (the attribute applies to all the type's methods).

  • 屬性 get 存取子。Property get accessors.

  • 運算子 (名稱開頭為 "op",並且有一個或兩個參數以及非 void 傳回型別的靜態方法)。Operators (static methods whose names start with "op", and that have one or two parameters and a non-void return type).

  • 完整名稱開頭為 "System.Diagnostics.Contracts.Contract"、"System.String"、"System.IO.Path" 或 "System.Type" 的任何方法。Any method whose fully qualified name begins with "System.Diagnostics.Contracts.Contract", "System.String", "System.IO.Path", or "System.Type".

  • 任何叫用的委派,前提是委派類型本身是以 PureAttribute 來屬性化。Any invoked delegate, provided that the delegate type itself is attributed with the PureAttribute. 委派類型 System.Predicate<T>System.Comparison<T> 會被視為單純。The delegate types System.Predicate<T> and System.Comparison<T> are considered pure.

可視性Visibility

合約中提及之所有成員的可視性,必須至少與其所在的方法相同。All members mentioned in a contract must be at least as visible as the method in which they appear. 例如,針對公用方法,不能在前置條件中提及私用欄位;用戶端無法在呼叫該方法之前,先驗證這類合約。For example, a private field cannot be mentioned in a precondition for a public method; clients cannot validate such a contract before they call the method. 不過,如果該欄位標示 ContractPublicPropertyNameAttribute,則不受限於這些規則。However, if the field is marked with the ContractPublicPropertyNameAttribute, it is exempt from these rules.

範例Example

下列範例顯示程式碼合約的用法。The following example shows the use of code contracts.

#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