У меня есть спор с инструментом статического анализа CodeContracts.
Мой код:
Инструмент говорит мне это instance.bar
может быть нулевая ссылка. Я верю противоположному.
Кто прав? Как я могу доказать его неправильно?
Update: Похоже, проблема в том, что инварианты не поддерживаются для статических полей.
2-е обновление: Метод, описанный ниже, является в настоящее время рекомендуемым решением.
Возможным обходным решением является создание свойства для экземпляра
, которое обеспечивает
инварианты, которые вы хотите сохранить. (Конечно, вам нужно Предположить
их, чтобы Обеспечить
было доказано). Как только вы это сделаете, вы можете просто использовать свойство, и все инварианты будут доказаны правильно.
Вот ваш пример с использованием этого метода:
class Foo
{
private static readonly Foo instance = new Foo();
private readonly string bar;
public static Foo Instance
// workaround for not being able to put invariants on static fields
{
get
{
Contract.Ensures(Contract.Result<Foo>() != null);
Contract.Ensures(Contract.Result<Foo>().bar != null);
Contract.Assume(instance.bar != null);
return instance;
}
}
public Foo()
{
Contract.Ensures(bar != null);
bar = "Hello world!";
}
public static int BarLength()
{
Contract.Assert(Instance != null);
Contract.Assert(Instance.bar != null);
// both of these are proven ok
return Instance.bar.Length;
}
}
CodeContracts верен. Вам ничего не мешает установить instance.bar = null
перед вызовом метода BarLength ()
.
Ваш код включает частный статический инициализированный экземпляр:
private static Foo instance = new Foo();
Предполагаете ли вы, что это означает, что конструктор экземпляра всегда будет запускаться перед доступом к любому статическому методу, что гарантирует bar
был инициализирован?
В однопоточном случае, я думаю, вы правы.
Последовательность событий будет следующей:
Foo.BarLength ()
Foo
(если еще не завершена) экземпляр
с экземпляром Foo
Foo.BarLength ()
Однако статическая инициализация класса запускается только один раз для каждого домена приложения - и IIRC нет никакой блокировки, чтобы гарантировать, что он завершен перед вызовом любых других статических методов.
Итак, у вас может быть такой сценарий:
Foo.BarLength ()
Foo
(если еще не завершена) запускает Foo.BarLength ()
Foo
, потому что это уже выполняется Foo.BarLength ()
null
Анализатор контрактов не может знать об этом что вы никогда не будете запускать код в многопоточном режиме, поэтому следует проявлять осторожность.
Если вы пометите поле bar как доступное только для чтения, это должно удовлетворить статический анализатор, так как это поле никогда не будет установлено на что-либо еще после выполнения ctor.
Я согласен с вами. instance
и bar
являются частными, поэтому CodeContracts должен знать, что instance.bar
никогда не имеет значения null.