Porque é que este Código C# do Contrato mal formado?
-
20-09-2019 - |
Pergunta
O Visual Studio mostra um erro quando eu escrever este contrato abaixo.
Erro 20 Malformado do contrato seção no método '....get_Page'
É o problema com o 'se' bloco?
public int? Page
{
get
{
int? result = Contract.Result<int?>();
if (result != null)
Contract.Ensures(result >= 0);
return default(int?);
}
}
EDITAR:
Lasse V.Karisen postou nos comentários:
Como sobre:Contrato.Assegura(resultado == null || resultado >= 0);?
Sim Karisen, eu tentei isso antes e compila-lo.Mas a pergunta permanece:não é possível ter ifs quando usando contratos?
Outro problema que eu estou tendo é um ignorante (principalmente considerando o exemplo acima funciona), envolve o uso de resultado também:
public int IndexOf(T item)
{
Contract.Assert(item != null);
Contract.Assert((item as IEntity).ID > 0);
int result = Contract.Result<int>();
Contract.Ensures(result >= -1);
return default(int);
}
Solução
Apenas tendo um palpite. Talvez deva ser Contract.Ensures(result.Value >= 0)
?
Outras dicas
O contrato é malformado porque todas as cláusulas de contrato devem aparecer perante qualquer outro código.
Você não precisa de um se, para fazer a manipulação booliana, o uso implica!
public int? Page
{
get
{
Contract.Ensures( (result!= null).Implies(result >= 0) );
var result = ...
...
return result;
}
}
Além disso, você deve usar requer não afirmar ao testar argumentos do método e outras pré -condições.
public int IndexOf(T item)
{
Contract.Requires(item != null);
Contract.Requires((item as IEntity).ID > 0);
...
Contrato tem sinalizador de compilação condicional neles. Em Liberação, mais seu código
if condition
contract
return
torna-se
if condition
return
Você vê o problema agora?
Todos os Garante e Exige chamadas deve ser antes de todas as outras declarações em um método ou uma propriedade do corpo, isso inclui atribuições simples como você está usando que ajudam a legibilidade.
Sintaxe adequada
public int? Page {
get {
Contract.Ensures(Contract.Result<int?>() == null
|| Contract.Result<int?>() >= 0);
return default(int?);
}
}
}
Este é muito feio, muito mais feia do que o normal if (x || y) throw new ArgumentOutOfRangeException()
.
Atributos Especiais
Há um pouco rotunda maneira de contornar isso. ContractAbbreviatorAttribute
e ContractArgumentValidatorAttribute
são os atributos especiais de que o ccrewrite
entende que tornam a sua vida mais fácil.(Para lotes com mais detalhes, veja a System.Diagnostics.Contracts
espaço de nomes de documentação no MSDN ou de Contratos de Código manual.)
Se utilizar .NET 4 ou mais:
Esses atributos são, no âmbito de partida .NET 4.5, mas para versões anteriores, você pode obter um arquivo de origem para eles a partir do directório de Contratos de Código instala.(C:\Program Files (x86)\Microsoft\Contracts\Languages\
) Em que pasta estão CSharp
e VisualBasic
subpastas que têm um ContractExtensions.cs
(ou .vb) arquivo que contém o código necessário.
ContractAbbreviatorAttribute Este atributo efetivamente permite que você crie contrato de macros.Com isso, a sua página de propriedade poderia ser escrito assim:
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
poderia, também, ser mantidos em uma classe estática e reutilizados em seu projeto, ou divulgado e colocado em uma biblioteca de utilitários.Você também pode torná-lo mais geral, como o seguinte exemplo.
[ContractAbbreviator]
static void EnsuresNullOrPositive<Nullable<T>>(Nullable<T> obj) {
Contract.Ensures(
Contract.Result<Nullable<T>>() == null ||
Contract.Result<Nullable<T>>() >= default(T));
}
Para minha própria biblioteca de utilitários, eu tenho uma classe estática chamada Requires
e uma classe estática chamada Ensures
, cada uma com vários métodos estáticos decorados com ContractAbbreviator
.Aqui estão alguns exemplos:
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);
}
}
Estes podem ser usados como este:
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
Eu não usei essa fora de tutoriais, mas basicamente ele permite a você escrever pacote de vários if (test) throw new ArgumentException()
chamadas em uma única chamada, que se comporta como uma chamada para Contract.Requires
.Uma vez que lida apenas com o argumento de validação, ele não ajuda com o seu pós-condição de exemplo.