Решил написать небольшую заметку после пары часов разбирательств — в сети ответы находятся не сразу, кусочками и на английском.
Про Microsoft CodeContracts на Хабре уже писали, это библиотека и инструментарий для Visual Studio, позволяющие использовать в C# элементы «контрактного программирования».
Мы начали использовать CodeContracts (далее — просто «контракты») в своих проектах относительно недавно, и, в целом, довольны, хоть и получили дополнительных несколько секунд ожидания к времени компиляции.
Ну, и, конечно, мы используем ReSharper, который в дополнительном представлении не нуждается.
Но есть пара нюансов, заключающихся в том, что для эффективной работы эти два инструмента нужно немножко подружить между собой.
Первым делом вы заметите, что по-умолчанию ReSharper думает, что некоторые вызовы контрактов типа нижеследующих будут исключены из сборки, и нещадно «засеривает» их:
На самом деле эти вызовы не исключаются из сборки. Некоторые методы контрактов, типа
После обычной компиляции механизмы контрактов инструментируют получившийся IL-код, и заменяют эти вызовы на другие, из класса
А вот вызовы
Так как же объяснить ReSharper'у, что
ReSharper тут же «увидит» недостающие методы! Конечно, не слишком удобно повторять значение флажков, уже выставленных на одной вкладке, еще и на другой вкладке, но, по крайней мере, это можно сделать один раз на конфигурацию проекта.
В JetBrains уже знают об этой проблемке, и, можно надеяться, в следующих версиях контракты будут поддержаны ReSharper'ом «из коробки».
Еще одна настройка, которую необходимо сделать для максимального использования возможностей ReSharper с контрактами — это настройка анализа значений (value analysis). В частности, ReSharper умеет выявлять наличие/отсутствие проверок параметров метода на
При этом по-умолчанию ReSharper не может догадаться, что добавление контрактного предусловия
К счастью, в ReSharper предусмотрен механизм т.н. External annotations, который позволяет разметить классы и методы из сторонних библиотек дополнительной метаинформацией для ReSharper'а. Конкретно, чтобы ReSharper начал «узнавать» проверки контрактов, нужно ему в папку "\Bin\ExternalAnnotations\mscorlib\" добавить специальный xml-файлик с метаописанием методов класса
После этого все становится на свои места, и вы снова с удовольствием можете довериться подсказкам любимого инструмента, не держа в голове случаи, когда он заблуждается из-за применения CodeContracts!
Про Microsoft CodeContracts на Хабре уже писали, это библиотека и инструментарий для Visual Studio, позволяющие использовать в C# элементы «контрактного программирования».
Мы начали использовать CodeContracts (далее — просто «контракты») в своих проектах относительно недавно, и, в целом, довольны, хоть и получили дополнительных несколько секунд ожидания к времени компиляции.
Ну, и, конечно, мы используем ReSharper, который в дополнительном представлении не нуждается.
Но есть пара нюансов, заключающихся в том, что для эффективной работы эти два инструмента нужно немножко подружить между собой.
Нюанс 1. Method invocation is skipped
Первым делом вы заметите, что по-умолчанию ReSharper думает, что некоторые вызовы контрактов типа нижеследующих будут исключены из сборки, и нещадно «засеривает» их:
На самом деле эти вызовы не исключаются из сборки. Некоторые методы контрактов, типа
Contract.Requires
и Contract.Ensures
, помечены атрибутом [Conditional("CONTRACTS_FULL")]
, но при этом флажок CONTRACTS_FULL
выставляется механизмом контрактов перед компиляцией, уже на этапе сборки проекта, и поэтому не виден на этапе редактирования ReSharper’у, который смотрит на перечень заданных символов компиляции в настройках.После обычной компиляции механизмы контрактов инструментируют получившийся IL-код, и заменяют эти вызовы на другие, из класса
__ConstractRuntime
. Подробнее это можно легко посмотреть Reflector'ом (торопитесь, пока он еще бесплатный).А вот вызовы
Contract.Assert
присутствуют в коде с самого начала, и они же работают во время выполнения, без переписывания. Они помечены не только как [Conditional("CONTRACTS_FULL")]
, но еще и как Conditional("DEBUG")
. В результате — для отладочной конфигурации (где определен символ DEBUG
) ReSharper и сам понимает, что этот вызов — настоящий.Так как же объяснить ReSharper'у, что
Contract.Requires
— тоже «честный» вызов, и его не следует «засеривать» в редакторе? Нужно просто для той конфигурации проекта, в которой у вас включены проверки контрактов времени выполнения (опции проекта «Code Contracts» > «Runtime Checking») выставить также вручную символ условной компиляции CONSTRACTS_FULL
на вкладке «Build».ReSharper тут же «увидит» недостающие методы! Конечно, не слишком удобно повторять значение флажков, уже выставленных на одной вкладке, еще и на другой вкладке, но, по крайней мере, это можно сделать один раз на конфигурацию проекта.
В JetBrains уже знают об этой проблемке, и, можно надеяться, в следующих версиях контракты будут поддержаны ReSharper'ом «из коробки».
Нюанс 2. Possible NullReferenceException
Еще одна настройка, которую необходимо сделать для максимального использования возможностей ReSharper с контрактами — это настройка анализа значений (value analysis). В частности, ReSharper умеет выявлять наличие/отсутствие проверок параметров метода на
null
и, соответственно, предупреждает о возможном выбросе исключения NullReferenceException
при обращении к методам объекта-аргумента при отсутствии такой проверки: При этом по-умолчанию ReSharper не может догадаться, что добавление контрактного предусловия
Contract.Requires(visitor != null)
обеспечивает необходимую проверку на null
.К счастью, в ReSharper предусмотрен механизм т.н. External annotations, который позволяет разметить классы и методы из сторонних библиотек дополнительной метаинформацией для ReSharper'а. Конкретно, чтобы ReSharper начал «узнавать» проверки контрактов, нужно ему в папку "\Bin\ExternalAnnotations\mscorlib\" добавить специальный xml-файлик с метаописанием методов класса
Contract
:<assembly name="mscorlib">
<member name="M:System.Diagnostics.Contracts.Contract.Assert(System.Boolean)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Assert(System.Boolean, System.String)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Assume(System.Boolean)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Assume(System.Boolean, System.String)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Requires(System.Boolean)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Requires``1(System.Boolean)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Requires(System.Boolean,System.String)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Requires``1(System.Boolean,System.String)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Invariant(System.Boolean)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
<member name="M:System.Diagnostics.Contracts.Contract.Invariant(System.Boolean,System.String)">
<attribute ctor="M:JetBrains.Annotations.AssertionMethodAttribute.#ctor"/>
<parameter name="condition">
<attribute ctor="M:JetBrains.Annotations.AssertionConditionAttribute.#ctor(JetBrains.Annotations.AssertionConditionType)">
<argument>0</argument>
</attribute>
</parameter>
</member>
</assembly>
После этого все становится на свои места, и вы снова с удовольствием можете довериться подсказкам любимого инструмента, не держа в голове случаи, когда он заблуждается из-за применения CodeContracts!