Понимание контрактов кода для интерфейсов

Я хочу создать контракт кода для определенного интерфейса, но мне трудно поверить, что это действительно так.

[ContractClass(typeof(AsyncCacheProviderContract))]
public interface IAsyncCacheProvider {

    Task<bool> GetAsync<T>(string key, out T value);

}

[ContractClassFor(typeof(AsyncCacheProviderContract))]
internal abstract class AsyncCacheProviderContract : IAsyncCacheProvider {

    public Task<bool> GetAsync<T>(string key, out T value)
    {
        Contract.Requires(!String.IsNullOrEmpty(key));

        value = default(T);
        return Task.Factory.StartNew(() => false);
    }

}

Контракт должен гарантировать, что 1) все классы, реализующие интерфейс, требуют, чтобы ключ аргумента не был нулевым или пустым, а также 2) автоматически генерировали проверку в сборке, например, аналогично

public Task<bool> GetAsync<T>(string key, out T value) {
    if(String.IsNullOrEmpty(key))
        throw new ArgumentException //...
}

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

1 ответ

Решение

Мне кажется странным, что я должен назначить аргумент out, а также вернуть фиктивную задачу, просто чтобы сделать компилятор счастливым.

Вам не нужно, если вы throw исключение вместо returnиз метода. Тогда вам не нужно создавать возвращаемое значение и не нужно присваивать out параметр:

[ContractClassFor(typeof(IAsyncCacheProvider))] // note: there's a small change here!
sealed class AsyncCacheProviderContract : IAsyncCacheProvider
{
    public Task<bool> GetAsync<T>(string key, out T value)
    {
        Contract.Requires(!String.IsNullOrEmpty(key));
        throw new NotSupportedException(); // makes the compiler happy, too
    }

    private AsyncCacheProviderContract() { } // optional safeguard:
}                                            // prevent instantiation (see below)

На самом деле это семантически более правильно, чем возвращение из метода. Зачем? Потому что никто не должен вызывать эти методы контракта. Они никогда не должны были выполнять какую-либо значимую работу, поэтому им не нужно возвращать никаких значимых значений. Все, что требуется от вашего класса контрактов, - это то, что он объявляет контракты; фактическая работа сделана где-то еще.

Связанный вопрос:
Реализация непустого интерфейса Code Contracts - по умолчанию (T) против throw NotImplementedException

Другие вопросы по тегам