Кодекс контрактов и проблемы наследования, что идет куда? - PullRequest
8 голосов
/ 13 января 2012

Возможно, я неправильно понимаю контракты с кодами, но вот моя ситуация.

У меня есть следующий код:

interface IFetch<T>    // defined in another DLL
{
    T Fetch(int id);
}

interface IUserFetch : IFetch<User>
{
    IEnumerable<User> GetUsersLoggedIn ();
}

class UserFetch : IUserFetch
{
    public User Fetch(int id)
    {
        return (User) Database.DoStuff (id);
    }

    public IEnumerable<User> GetUsersLoggedIn ()
    {
        return (IEnumerable<User>) Database.DoMoreStuff ();
    }
}

Я пытаюсь добавить относительно простой контракт: Contract.Requires (id != 0);, и я хочу, чтобы он был проверен на Fetch. Когда я добавляю его в Fetch напрямую, я получаю предупреждение, что Method Fetch(int id) implements interface 3rdParty.IFetch<User> and thus cannot add Requires.

Я создал класс контрактов абстрактного кода, реализующий IFetch, и указал его на / из UserFetch, используя атрибуты ContractClass и ContractClassFor соответственно. Я по-прежнему получаю сообщение об ошибке, подобное CodeContracts: The class 'FetchUserContracts' is supposed to be a contract class for '3rdParty.IFetch<User>', but that type does not point back to this class. Однако, поскольку 3rdParty.IFetch является универсальным типом, я не думаю, что когда-либо смогу специально заключить с ним контрактный код.

Была ли проблема прояснена, и если да, то как ее решить?

Ответы [ 2 ]

10 голосов
/ 13 января 2012

Вам нужно создать абстрактный класс для реализации контракта, например:

[ContractClassFor(typeof(IFetch<>))]
public abstract class ContractClassForIFetch<T> : IFetch<T>
{
    public T Fetch(int id)
    {
        Contract.Requires(id != 0);
        return default(T);
    }
}

И добавьте следующий атрибут ContractClass в IFetch:

[ContractClass(typeof(ContractClassForIFetch<>))]
public interface IFetch
{
    T Fetch(int id);
}
5 голосов
/ 13 января 2012

Я верю, что Ɖiamond Ǥeeze ответ правильный.Я просто добавлю немного пояснений.

Вы не можете добавить украшение Contract.Requires к методу в замкнутом построенном типе (например, IFetch<User>).Вы должны добавить его к открытому построенному типу (IFetch<>).Причиной этого является та же самая причина, по которой вы не можете добавить украшение Contract.Requires к конкретному методу, который используется для реализации интерфейса: контракты кода предназначены для проверки во время компиляции, когда полный тип экземпляра объекта может бытьнеизвестно.

Предположим, это позволило вам заключить договор на реализацию конкретного метода.

public User Fetch(int id) 
{ 
    Contract.Requires (id != 0);, 
    return (User) Database.DoStuff (id); 
} 

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

class DataDisplayer<T>
{
    Label myLabel = new Label();
    public void Display(IFetch<T> fetch, int id)
    {
        myLabel.Text = fetch.Fetch(id).ToString();
    }
}

Допускает ли это нарушение договора или нет?Невозможно сказать, потому что мы не знаем, каким будет конкретный тип fetch во время выполнения.

Заключение контракта для определенного закрытого составного типа, такого как IFetch<User>, не решает эту проблему.Мы до сих пор не знаем, какой тип T в коде вызова.

Принцип подстановки Лискова, являющийся краеугольным камнем всего объектно-ориентированного программирования, означает, что мы никогда не можем предполагать что-либо о типе времени выполнения объекта, кроме того, что указывает тип переменной.А так как тип переменной может быть универсальным интерфейсом, контракты кода не могут наложить на вызывающего абонента какую-либо дополнительную нагрузку, чем в определении универсального интерфейса.

Следовательно, Contract.Requires должно быть помещено в открытый составной тип,так что любой объект, реализующий интерфейс, будет иметь требование, и любой потенциальный вызов метода через переменную типа интерфейса может быть проверен на корректность по отношению к этому требованию.

...