Небольшой пример использования Code Contract

    • .NET
    • C#
    • .NET 4.0
    • WPF
    • Code Contract
    • ReSharper
    • Code Contracts
  • modified:
  • reading: 3 minutes

Признаюсь честно, что с темой Code Contract в .Net 4 я знаком давно, но вот реально ее использовать мне что-то не очень-то хотелось. Привык я к простым и обычным проверкам, вроде

if (argument == null) 
    throw new ArgumentNullException(“argument”)

А вот большого плюса от контрактов я не увидел. И вот недавно переставлял себе Windows из-за счастливого приобретения Intel® X25-M, и так получилось что случайно выбрал в фиче R# или Visual Studio 2010 при щелчке на классе левой кнопкой мышки при удержании Ctrl переход на метаданные, а не как обычно Object Explorer. И вот как-то перейдя на один из классов System.xxx попался мне исходный код (метаданные) одного из базовых классов .Net, там я заметил, что контракты распространенно используются в базовых классах фреймворка. Кстати, кто-нибудь знает как сделать так, чтобы при щелчке на классе с зажатым Ctrl все-таки переходить в Object Explorer?

Решил я, что надо бы тоже начать с ними знакомство в более тесной манере, и начать их использовать повсеместно. И вот в предыдущем посте я и использовал их впервые. Если кто не знает, чтобы начать использовать контракты у себя в проекте, точнее чтобы они начали работать, лучше всего скачать Code Contracts Editor Extensions и при помощи этого плагина произвести настройку проекта (этот плагин добавить две дополнительные вкладки к настройках проектов).

Итак, давайте рассмотрим пример. Пускай у нас есть такой вот класс:

public sealed class HotKey 
{    private IntPtr _handle;
     public HotKey(Window window)
        :this (new WindowInteropHelper(window))
    {
    }
     public HotKey(WindowInteropHelper window)
        :this(window.Handle)
    {
    }
     public HotKey(IntPtr windowHandle)
    {
        _handle = windowHandle;
    }
}

То есть, идеально нам нужно получить Handle окна, и мы позволяем инициализировать наш класс, либо самим окном, либо WindowInteropHelper, либо уже готовым Handle. Но что будет, если пользователь, например, попытается создать новый объект типа HotKey передав туда null в качестве Window (используя первый конструктор)? Мы получим сообщение, вроде “Value cannot be null. Parameter name: window”, его нам бросит конструктор типа WindowInteropHelper. Нам просто повезло, что вроде можно будет разобраться, почему возникла ошибка. Но вот если передать null в качестве WindowInteropHelper, то есть во второй конструктор, мы получим уже непонятную ошибку “Object reference not set to an instance of an object”. Как нам этого избежать? Можно при помощи отдельного метода Initialize:

public sealed class HotKey
{    private IntPtr _handle;
     public HotKey(Window window)
    {        if (window == null)
            throw new ArgumentNullException("window");
         Initialize(new WindowInteropHelper(window).Handle);
    }
     public HotKey(WindowInteropHelper window)
    {        if (window == null)
            throw new ArgumentNullException("window");
 
        Initialize(window.Handle);
    }
     public HotKey(IntPtr windowHandle)
    {
        Initialize(windowHandle);
    }
     private void Initialize(IntPtr windowHandle)
    {
        _handle = windowHandle;
    }
}

А можно все-таки воспользоваться контрактами:

public sealed class HotKey 
{    private IntPtr _handle;
     public HotKey(Window window)
        : this (new WindowInteropHelper(window))
    {        Contract.Requires(window != null);
    }
     public HotKey(WindowInteropHelper window)
        : this(window.Handle)
    {        Contract.Requires(window != null);
    }
     public HotKey(IntPtr windowHandle)
    {
        _handle = windowHandle;
    }
}

И, вроде, выглядит все так, что, например, во втором конструкторе все равно произойдет ошибка NullReferenceException, если передать null, до того, как начнет выполнятся код конструктора. И даже R# нам практически об этом говорит (надо бы, наверное создать багу на JetBrains), что не стоит делать сравнение с null, так как до этого и так уже используете переменную, считая что она null быть не может:

Untitled

Но на самом деле, контракты проверяются до вызова метода, поэтому мы увидим при запуске приложения при инициализации null:

Capture

В общем, дальше только с контрактами.

See Also