Рассмотрим две ситуации, которые могут произойти, если в проекте одновременно используются асинхронные методы и контракты.
Актуальная версия Code Contracts для данного материала – 1.7.10908.11. Скорее всего, описанные ошибки в дальнейшем будут исправлены.
Постусловия в async
Как многим хорошо известно, использование async/await раскрывает выброшенные из асинхронного кода AggregateException. Поэтому код с ними зачастую выглядит примерно так:
try {
…
var result = await someService.SomeActionAsync(currentData);
…
}
catch (ArgumentException ex) {
// Обработка ошибки
}
Однако, при использовании CodeContracts такой код может начать выдавать AggregateException. И тогда код может превратиться в что-то вроде:
try {
…
var result = await someService.SomeActionAsync(currentData);
…
}
catch (ArgumentException ex) {
// Обработка ошибки
}
catch (AggregateException ex) {
var argumentException = ex.InnerExceptions.FirstOrDefault(e => e is ArgumentException);
if (argumentException != null) {
// Обработка ошибки
}
else throw;
}
На первый взгляд это похоже на ошибку работы await, который перестал раскрывать AggregateException. Но не будем спешить с выводами и посмотрим на метод SomeActionAsync(). Скорее всего он содержит постусловие, например:
Contract.Ensures(0 < Contract.Result<int>());
В этом случае в генерируемом коде класса, который его содержит, можно найти следующую обертку для проверки контрактов:
[CompilerGenerated]
private class <SomeAction>AsyncContractClosure_0
{
public <SomeAction>AsyncContractClosure_0() { }
public int CheckPost(Task<int> task)
{
Task<int> task1 = task;
int result = task.Result;
if (__ContractsRuntime.insideContractEvaluation <= 4) {
try {
__ContractsRuntime.insideContractEvaluation = __ContractsRuntime.insideContractEvaluation + 1;
__ContractsRuntime.Ensures(0 < task1.Result, null, "0 < Contract.Result<int>()");
}
finally {
__ContractsRuntime.insideContractEvaluation = __ContractsRuntime.insideContractEvaluation - 1;
}
}
return result;
}
}
Вызов task.Result приводит к тому, что исходное AggregateException (из самого метода) оборачивается в еще одно AggregateException. Используемый await честно раскрывает внешнее и передает вложенное AggregateException дальше.
На данный момент решений данной проблемы два: отказаться от постусловий или писать обработку как конкретных исключений, так и AggregateException.
Пропадание переменных в отладке
Еще один не приятный эффект – пропадание значений переменных при пошаговой отладке. Visual Studio не может отобразить их даже в режиме Debug. Такое поведение замечено в методах, содержащих несколько вызовов await у которых есть контракты. Судя по всему, CodeContracts сильно изменяют код с помощью CCRewrite.
Решение для данной проблемы – временное отключение CodeContracts для отладки метода, где наблюдается такое поведение.