Все вызовы «Обеспечивает и требует» должны быть перед всеми другими операторами в теле метода или свойства, включая простые назначения, которые вы используете, чтобы помочь читабельности.
Правильный синтаксис
public int? Page {
get {
Contract.Ensures(Contract.Result<int?>() == null
|| Contract.Result<int?>() >= 0);
return default(int?);
}
}
}
Это очень некрасиво , намного страшнее, чем обычно if (x || y) throw new ArgumentOutOfRangeException()
.
Специальные атрибуты
Есть несколько окольный способ обойти это. ContractAbbreviatorAttribute
и ContractArgumentValidatorAttribute
- это особые атрибуты, которые ccrewrite
понимают, которые облегчают вашу жизнь. (Подробные сведения см. В документации по пространству имен System.Diagnostics.Contracts
на MSDN или в Code Contracts , руководство .)
При использовании .NET 4 или старше:
Эти атрибуты находятся в платформе, начиная с .NET 4.5, но для предыдущих версий вы можете получить исходный файл для них из каталога, в который устанавливается Code Contracts. (C:\Program Files (x86)\Microsoft\Contracts\Languages\
) В этой папке находятся подпапки CSharp
и VisualBasic
с файлом ContractExtensions.cs
(или .vb), содержащим необходимый код.
ContractAbbreviatorAttribute
Этот атрибут позволяет эффективно создавать макросы контрактов. При этом свойство вашей страницы может быть записано так:
public int? Page {
get {
EnsuresNullOrPositive();
return default(int?)
}
}
[ContractAbbreviator]
static void EnsuresNullOrPositive(int? x) {
Contract.Ensures(
Contract.Result<int?>() == null ||
Contract.Result<int?>() >= 0);
}
EnsuresNullOrPositive
также может храниться в статическом классе и использоваться повторно в вашем проекте, или может быть опубликован и помещен в служебную библиотеку. Вы также можете сделать его более общим, как в следующем примере.
[ContractAbbreviator]
static void EnsuresNullOrPositive<Nullable<T>>(Nullable<T> obj) {
Contract.Ensures(
Contract.Result<Nullable<T>>() == null ||
Contract.Result<Nullable<T>>() >= default(T));
}
Для моей собственной служебной библиотеки у меня есть статический класс с именем Requires
и статический класс с именем Ensures
, каждый со многими статическими методами, украшенными ContractAbbreviator
. Вот несколько примеров:
public static class Requires {
[ContractAbbreviator]
public static void NotNull(object obj) {
Contract.Requires<ArgumentNullException>(obj != null);
}
[ContractAbbreviator]
public static void NotNullOrEmpty(string str) {
Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(str));
}
[ContractAbbreviator]
public static void NotNullOrEmpty(IEnumerable<T> sequence) {
Contract.Requires<ArgumentNullException>(sequence != null);
Contract.Requires<ArgumentNullException>(sequence.Any());
}
}
public static class Ensures {
[ContractAbbreviator]
public static void NotNull(){
Contract.Ensures(Contract.Result<object>() != null);
}
}
Их можно использовать так:
public List<SentMessage> EmailAllFriends(Person p) {
Requires.NotNull(p); //check if object is null
Requires.NotNullOrEmpty(p.EmailAddress); //check if string property is null or empty
Requires.NotNullOrEmpty(p.Friends); //check if sequence property is null or empty
Ensures.NotNull(); //result object will not be null
//Do stuff
}
ContractArgumentValidatorAttribute
Я не использовал этот за пределами учебников, но в основном он позволяет вам написать пакет из нескольких вызовов if (test) throw new ArgumentException()
за один вызов, который ведет себя как вызов Contract.Requires
. Поскольку он имеет дело только с проверкой аргументов, он не поможет с вашим примером после условия.