web-dev-qa-db-ja.com

.NET Coreでコードコントラクトを適切に使用する方法

。NET Coreでコードコントラクトを適切に使用するにはどうすればよいのでしょうか。 Contract.Requiresを使用する各呼び出しに表示されるメッセージと、グーグルで見つけた情報に混乱しています。

メッセージの状態:

Contract.Requires<TException>を呼び出しており、CONTRACTS_FULLシンボルが定義されているため、アセンブリはコードコントラクトバイナリリライタ(CCRewrite)を使用して書き換える必要があります。プロジェクトからCONTRACTS_FULLシンボルの明示的な定義を削除して、再構築します。 CCRewrite ....

ご覧のとおり、プロジェクトのプロパティにはCCオプションがありません。また、CCのGithubリポジトリはほぼ消滅しています。 .NET CoreでCCを正常に使用する方法はありますか?

そうでない場合、それらを置き換える簡単な方法はありますか? Contract.RequiresContractClassAttributeを使用します。 Contract.Requiresを置き換えることは明らかですが、ContractClassAttributeは私の心を吹き飛ばします:-)

25

まず、 Microsoft docs に従って、CodeContractsが何であるかを理解しましょう。

コードコントラクトは、コードで前提条件、事後条件、およびオブジェクト不変条件を指定する方法を提供します。前提条件は、メソッドまたはプロパティを入力するときに満たす必要がある要件です。事後条件は、メソッドまたはプロパティコードが終了する時点での期待を表します。オブジェクト不変条件は、良好な状態にあるクラスの予想される状態を記述します。

つまり、物事を簡単にするために、CodeContractsはコード内のテストを単純化するのに役立ちます。

コードコントラクトの使用方法

この例を考えてみましょう:

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

preconditionsは、2つのケースのうちの1つとはどういう意味ですか?

  • ステートメントは、メソッド内の他のステートメントの前に表示されます。
  • そのようなステートメントのセット全体の後に、明示的な Contract メソッド呼び出し( RequiresEnsuresEnsuresOnThrow 、または EndContractBlock メソッド。

_if-then-throw_ステートメントがこの形式で表示される場合、ツールはそれらをレガシーのrequireステートメントとして認識します。 if-then-throwシーケンスに従う他のコントラクトがない場合は、 Contract.EndContractBlock メソッドでコードを終了します。


Postconditionsでも使用できます。

Postconditionsとは

事後条件は、メソッドが終了したときの状態のコントラクトです。事後条件は、メソッドを終了する直前にチェックされます。失敗した事後条件の実行時の動作は、ランタイムアナライザーによって決定されます。

前提条件とは異なり、事後条件は可視性の低いメンバーを参照する場合があります。クライアントは、プライベート状態を使用した事後条件によって表される情報の一部を理解または利用できない場合がありますが、これはクライアントがメソッドを正しく使用する能力に影響しません。

つまり、事を短くするために、事後条件はメソッドをテストするのに役立ちます。

例は次のとおりです。

_Contract.Ensures( this.F > 0 );
_

特別な事後条件に注意してください:

  • Contract.Result<T>()を使用して、事後条件でメソッドの戻り値を参照できます。ここで、Tはメソッドの戻り値の型に置き換えられます。コンパイラが型を推測できない場合、明示的に提供する必要があります。
  • 事後条件の事前状態値は、メソッドまたはプロパティの開始時の式の値を指します。式Contract.OldValue<T>(e)を使用します。ここで、Teのタイプです。コンパイラーが型を推測できる場合は常に、ジェネリック型引数を省略できます。 (たとえば、C#コンパイラは引数を取るため、常に型を推測します。)eで発生する可能性のあるものと、古い式が現れるコンテキストにはいくつかの制限があります。古い式に別の古い式を含めることはできません。最も重要なことは、古い式はメソッドの前提条件状態に存在した値を参照する必要があることです。つまり、メソッドの前提条件が真である限り評価可能な式でなければなりません。

最後に、不変式があります:

オブジェクトの不変条件は、そのオブジェクトがクライアントに見えるときはいつでもクラスの各インスタンスに当てはまる条件です。それらは、オブジェクトが正しいと見なされる条件を表します。

つまり、不変式はクラスコードとインスタンスのテストに役立ちます。

例は次のとおりです。

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

CodeContractsを適切に使用するための完全な例:

_using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Net.Http.Headers;
using System.Diagnostics.Contracts;

namespace System.Net.Http
{
    public class FormUrlEncodedContent : ByteArrayContent
    {
        public FormUrlEncodedContent(IEnumerable<KeyValuePair<string, string>> nameValueCollection)
            : base(GetContentByteArray(nameValueCollection))
        {
            Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        }

        private static byte[] GetContentByteArray(IEnumerable<KeyValuePair<string, string>> nameValueCollection)
        {
            if (nameValueCollection == null)
            {
                throw new ArgumentNullException(nameof(nameValueCollection));
            }
            Contract.EndContractBlock();

            // Encode and concatenate data
            StringBuilder builder = new StringBuilder();
            foreach (KeyValuePair<string, string> pair in nameValueCollection)
            {
                if (builder.Length > 0)
                {
                    builder.Append('&');
                }

                builder.Append(Encode(pair.Key));
                builder.Append('=');
                builder.Append(Encode(pair.Value));
            }

            return HttpRuleParser.DefaultHttpEncoding.GetBytes(builder.ToString());
        }

        private static string Encode(string data)
        {
            if (String.IsNullOrEmpty(data))
            {
                return String.Empty;
            }
            // Escape spaces as '+'.
            return Uri.EscapeDataString(data).Replace("%20", "+");
        }

        internal override Stream TryCreateContentReadStream() =>
            GetType() == typeof(FormUrlEncodedContent) ? CreateMemoryStreamForByteArray() : // type check ensures we use possible derived type's CreateContentReadStreamAsync override
            null;
    }
}
_
2
Barr J