Как стать автором
Обновить

Tip: использование ReSharper совместно с Microsoft CodeContracts

Время на прочтение6 мин
Количество просмотров839
Решил написать небольшую заметку после пары часов разбирательств — в сети ответы находятся не сразу, кусочками и на английском.

Про Microsoft CodeContracts на Хабре уже писали, это библиотека и инструментарий для Visual Studio, позволяющие использовать в C# элементы «контрактного программирования».

Мы начали использовать CodeContracts (далее — просто «контракты») в своих проектах относительно недавно, и, в целом, довольны, хоть и получили дополнительных несколько секунд ожидания к времени компиляции.

Ну, и, конечно, мы используем ReSharper, который в дополнительном представлении не нуждается.

Но есть пара нюансов, заключающихся в том, что для эффективной работы эти два инструмента нужно немножко подружить между собой.

Нюанс 1. Method invocation is skipped

Первым делом вы заметите, что по-умолчанию ReSharper думает, что некоторые вызовы контрактов типа нижеследующих будут исключены из сборки, и нещадно «засеривает» их:

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 не понимает, что контракты проверяют аргумент на null

При этом по-умолчанию 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!
Теги:
Хабы:
Всего голосов 6: ↑6 и ↓0+6
Комментарии3

Публикации

Истории

Ближайшие события