Скажите FxCop, что другой метод вызывает dispose

Обычно, когда вы размещаете частный член, вы можете сделать следующее:

public void Dispose() {
    var localInst = this.privateMember;
    if (localInst != null) {
        localInst.Dispose();
    }
}

Цель локального назначения - избежать условия гонки, когда другой поток может назначить частному члену null после нулевой проверки. В этом случае мне все равно, если в экземпляре дважды вызывается Dispose.

Я использую этот шаблон все время, поэтому я написал метод расширения для этого:

public static void SafeDispose(this IDisposable disposable)
{
    if (disposable != null)
    {
        // We also know disposable cannot be null here, 
        // even if the original reference is null.
        disposable.Dispose();
    }
}

И теперь в моем классе я могу просто сделать это:

public void Dispose() {
    this.privateMember.SafeDispose();
}

Проблема заключается в том, что FxCop не имеет понятия, что я делаю это, и это дает мне CA2000: удалять объекты перед потерей видимости предупреждение в каждом случай.

Я не хочу отключать это правило, и я не хочу подавлять каждый случай. Есть ли способ намекнуть на FxCop, что этот метод эквивалентен Dispose насколько это касается?

Ответ 1

Короткий ответ: нет способа намекнуть, что объект находится в другом месте.

Немного отражается рефлектор (или точка-точка или что-то еще).

FxCop находится в C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop. (Соответственно, для вашей версии OS/VS версия.) Правила находятся в подкаталоге Rules.

В основной папке FxCop откройте

  • Microsoft.VisualStudio.CodeAnalysis.dll
  • Microsoft.VisualStudio.CodeAnalysis.Phoenix.dll
  • phx.dll

В папке Rules откройте DataflowRules.dll.

В DataflowRules.dll найдите Phoenix.CodeAnalysis.DataflowRules.DisposeObjectsBeforeLosingScope. Это фактический класс, который делает оценку.

Посмотрев на код, вы можете увидеть две вещи, представляющие интерес в отношении вашего вопроса.

  • Он использует общий сервис под названием SharedNeedsDisposedAnalysis.
  • Он происходит от FunctionBodyRule.

Первый элемент интересен тем, что SharedNeedsDisposedAnalysis - это то, что определяет, какие символы нужно вызвать Dispose().. Он довольно тщательно, "прогуливаясь" через код, чтобы определить, что нужно удалять и что фактически удаляется. Затем он сохраняет таблицу этих вещей для последующего использования.

Второй элемент интересен тем, что правила FunctionBodyRule оценивают тело одной функции. Существуют и другие типы правил, такие как FunctionCallRule, которые оценивают такие элементы, как члены вызова функции (например, ProvideCorrectArgumentsToFormattingMethods).

Дело в том, что между потенциальной "пропуском" в этой службе SharedNeedsDisposedAnalysis, где она не может быть рекурсивно через ваш метод, чтобы видеть, что вещи фактически находятся в распоряжении, а ограничение FunctionBodyRule не выходит за пределы тела функции, он просто не догоняет ваше расширение.

Это та же самая причина, по которой "функции защиты", такие как Guard.Against<ArgumentNullException>(arg), никогда не воспринимаются как проверка аргумента перед его использованием. FxCop все равно скажет вам проверить аргумент для null, даже если это то, что делает "функция защиты".

У вас есть в основном два варианта.

  • Исключить проблемы или отключить правило. Невозможно сделать то, что вы хотите.
  • Создать правило пользовательского/производного, которое будет понимать методы расширений. Используйте свое пользовательское правило вместо правила по умолчанию.

После того, как я сам напишу собственные правила FxCop, я дам вам знать, что нашел... нетривиальным. Если вы идете по этой дороге, а рекомендация в мире - использовать новый стиль правил двигателя Phoenix (то, что использует текущий DisposeObjectsBeforeLosingScope), мне было легче понять старшие/стандартные правила FxCop SDK (см. FxCopSdk.dll в основной папке FxCop). Отражатель будет огромной помощью в определении того, как это сделать, поскольку там почти нулевой документ. Посмотрите в других сборках в папке Rules, чтобы увидеть их примеры.

Ответ 2

Я вовсе не эксперт FxCop, но этот вопрос об использовании SuppressMessage отвечает на него? Я не знаю, может ли украшение вашего метода SafeDispose атрибутом SuppressMessage, чтобы FxCop подавлял это сообщение при анализе методов, которые его называют, но похоже, что это стоит того.

Не доверяйте синтаксису ниже, но что-то вроде:

[SuppressMessage("Microsoft.Design", "CA2000:Dispose objects before losing scope", Justification = "We just log the exception and return an HTTP code")]
public static void SafeDispose(this IDisposable disposable)

Ответ 3

Это правило анализа кода является проблематичным по всем причинам, изложенным Трэвисом. Кажется, что очередь на любую "новую" операцию, и если вызов dispose не закрыт, CA2000 запускается.

Вместо использования new вызовите метод с этим в теле:

MyDisposableClass result;
MyDisposableClass temp = null;
try
{
  temp = new MyDisposableClass();
  //do any initialization here
  result = temp;
  temp = null;
}
finally
{
  if (temp != null) temp.Dispose();
}
return result;

То, что это делает, - это удаление любой возможности инициализации, вызывающей недопустимость объекта для удаления. В вашем случае, когда вы "обновляете" privatemember, вы делаете это в методе, который выглядит как вышеупомянутый метод. После использования этого шаблона вы, конечно, все еще несете ответственность за правильное удаление, и ваш метод расширения - отличный способ обобщить эту нулевую проверку.

Я обнаружил, что вы можете избежать CA2000, все еще передавая IDisposables и делая то, что хотите с ними, - пока вы их правильно обновляете в рамках метода, подобного выше. Попробуйте и дайте мне знать, если это сработает для вас. Удачи, и хороший вопрос!

Другие исправления для этого правила (включая этот) описаны здесь: CA2000: удалять объекты перед потерей области (Microsoft)