首先,让我们了解一下什么是
CodeContracts,根据
microsoft docs的说法:
代码合同提供了一种在代码中指定前置条件、后置条件和对象不变量的方法。前置条件是进入方法或属性时必须满足的要求。后置条件描述了方法或属性代码退出时的期望结果。对象不变量描述了处于良好状态的类的预期状态。
也就是说,简单来说,CodeContracts 帮助我们简化代码中的测试。
我们如何使用代码合同?
考虑以下示例:
if ( x == null ) throw new ...
Contract.EndContractBlock(); // All previous "if" checks are preconditions
“preconditions” 的意思是两种情况之一:
当
if-then-throw
语句以这种形式出现时,工具会将其识别为传统的requires语句。如果没有其他合同跟随if-then-throw序列,请使用
Contract.EndContractBlock方法结束代码。
您可以在“后置条件”中使用它:
“后置条件”是指方法终止时的状态约定。在退出方法之前,将检查后置条件。运行时分析器确定了失败后置条件的运行时行为。
与先决条件不同,后置条件可能引用可见性较低的成员。客户端可能无法理解或利用使用私有状态表达的某些信息的后置条件,但这不会影响客户端正确使用该方法的能力。
换句话说,后置条件帮助我们测试我们的方法。
例如:
Contract.Ensures( this.F > 0 );
请注意特殊的后置条件:
- 您可以使用表达式
Contract.Result<T>()
引用方法返回值的后置条件,其中T
被替换为方法的返回类型。当编译器无法推断类型时,您必须显式提供它。
- 后置条件中的预状态值指方法或属性开始时表达式的值。它使用表达式
Contract.OldValue<T>(e)
,其中T
是e
的类型。每当编译器能够推断其类型时,您可以省略通用类型参数。(例如,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();
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;
}
return Uri.EscapeDataString(data).Replace("%20", "+");
}
internal override Stream TryCreateContentReadStream() =>
GetType() == typeof(FormUrlEncodedContent) ? CreateMemoryStreamForByteArray() :
null;
}
}