Анализ кода дружественный способ избавиться от объектов - PullRequest
7 голосов
/ 23 ноября 2011

В рамках наших стандартов разработки Visual Studio 2010 (прежде всего C # 4.0) у нас включен анализ кода. Поскольку я рассматриваю недавно представленный код для нового проекта, я вижу тонну

CA2000: Microsoft. Надежность: в методе 'XYZ' объект 'ABC' не является расположены вдоль всех путей исключения. Вызов System.IDisposable.Dispose on объект 'ABC' до того, как все ссылки на него выйдут из области видимости.

предупреждения. Проблема в том, что ничего, что я делаю, похоже, не устраняет предупреждения - и я часами рылся в Интернете и пробовал все, что мог.

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

Вот пример кода, который содержит четыре таких предупреждения:

public void MainMethod()
{
    var object1 = CreateFirstObject();    // Warning here
    var object2 = CreateSecondObject();   // Warning here

    SomeCollectionProperty.Add(object1);
    SomeCollectionProperty.Add(object2);
}

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject()      // Warning here
    {
        FirstProperty = "some value",
        // ...
    };

    return theObject;
}

private SomeOtherObject CreateSecondObject()
{
    var theObject = new SomeOtherObject() // Warning here
    {
        FirstProperty = "a different value",
        // ...
    };

    return theObject;
}

Я прокомментировал строки, где появляются предупреждения.

Я попытался реорганизовать оба метода Create, как описано в статье MSDN ( здесь ), но предупреждения по-прежнему появляются.

UPDATE Я должен отметить, что SomeObject и SomeOtherObject реализуют IDisposable.

Кроме того, хотя инициализаторы объектов могут быть компонентом проблемы, имейте в виду, что инициализаторы изолированы от двух частных методов и не имеют ничего общего с предупреждениями в MainMethod.

Может кто-нибудь показать мне, как правильно реализовать эти методы для устранения предупреждений CA2000?

Ответы [ 5 ]

10 голосов
/ 23 ноября 2011

Проблема, которая обнаруживается CA2000 в этом случае, состоит в том, что одноразовый экземпляр может быть «осиротевшим», если возникает исключение перед его передачей из метода.Например, «правильная» реализация CreateFirstObject будет выглядеть примерно так:

private SomeObject CreateFirstObject()
{
    var theObject = new SomeObject();
    try
    {
        theObject.FirstProperty = "some value";
    }
    catch
    {
        theObject.Dispose();
        throw;
    }

    return theObject;
}

Учитывая то, что вы описали относительно желаемого поведения MainMethod, его «правильная» реализация может выглядеть примерно такэто:

public void MainMethod()
{
    var object1 = CreateFirstObject();
    try
    {
        SomeCollectionProperty.Add(object1);

        var object2 = CreateSecondObject();
        try
        {
            SomeCollectionProperty.Add(object2);
        }
        catch
        {
            object2.Dispose();
            throw;
        }
    }
    catch
    {
        object1.Dispose();
        SomeCollectionProperty.Remove(object1); // Not supposed to throw if item does not exist in collection.

        throw;
    }
}
1 голос
/ 23 ноября 2011

Один из способов избавиться от предупреждения - подавить его в коде:

[SuppressMessage(
    "Microsoft.Reliability",
    "CA2000:DisposeObjectsBeforeLosingScope",
    Justification = "Factory method")]

Но это не настоящее решение проблемы.

Решение описано здесь: Как избавиться от предупреждения CA2000 при передаче права собственности?

В упомянутой ссылке в основном указано, что объект добавляется в коллекцию, реализующую ICollection<T>, но я этого не проверял.

0 голосов
/ 03 июля 2019

Шаблон, который мы используем и который очищает предупреждение большую часть времени равен

DisposableObject retVal;
DisposableObject tempVal;

try{
  tempVal = new DisposableObject();
  tempVal.DoStuff();
  retVal = tempVal;
  tempVal = null;
} finally{
  if (tempVal != null) { tempVal.Dispose();} //could also be writtent tempVal?.Dispose();
}

return retVal;

К сожалению, все еще есть некоторые случаи, когда предупреждение не срабатывает, поэтому мы удаляемлокально предупреждение с обоснованием, гласящее, что одноразовый материал покрыт рисунком.

Это очень кратко упоминается в Документация Microsoft .

0 голосов
/ 23 ноября 2011

Требуется реализовать шаблон, похожий на блок «using», но отключить удаление объекта в сценарии, в котором он будет успешно возвращен.Подход, предложенный Николь Калиною, является разумным, хотя я предпочитаю избегать ловли исключений, которые просто собираются всплыть.Мое предпочтительное выражение кода, учитывая ограничения языка C #, должно было бы использовать флаг InitializedSuccessfully, а затем иметь блок «finally», который заботится об удалении, если InitializedSuccessfully не был вызван.

Если класс будет содержать много объектов IDIsposable, и набор таких объектов будет исправлен после завершения построения, может быть полезно определить класс менеджера IDisposable, который будет содержать список объектов IDisposable.Пусть конструкторы вашего класса принимают объект DisposableManager в качестве параметра и помещают все объекты, которые он создает, в сгенерированный таким образом список (для вашего класса может быть полезно иметь метод экземпляра:

T regDisposable<T>RegDispose(T newThing) where T:IDisposable
{
  myDisposableManager.Add(newThing);
  return newThing;
}

Чтобы использовать его, после того, как myDisposableManager был инициализирован, просто скажите что-то вроде: var someDisposableField = RegDispose(new someDisposableType());. Такой шаблон даст два больших преимущества:

  1. Все, что должен сделать основной класс в своей реализации Dispose, будетbe `if (myDisposableManager! = null) myDisposableManager.Dispose ();` Установка объекта IDisposable в конструкторе (с использованием RegDispose) также обеспечит его очистку.
  2. Код, вызывающий конструктор для основного объекта, можетЕсли конструктор выдает исключение, вызовите метод Dispose для объекта DisposableManager, который он создал и передал. Это обеспечит своевременную очистку частично созданного объекта, что в противном случае практически невозможно.

InVB, это возможно дляконструктор базового класса для представления параметра конструктора в качестве поля, доступного для инициализаторов полей.Таким образом, можно красиво использовать шаблон RegDispose в инициализаторах полей, а также в явном конструкторе.В C # это невозможно.Для этой цели можно использовать поля [threadstatic], но потребуется некоторая осторожность, чтобы гарантировать, что любые такие поля, которые установлены, также становятся неустановленными.Конструктор вызывается из чего-то похожего на поток пула может вызвать утечку памяти.Кроме того, к потоковым полям нельзя получить доступ почти так же эффективно, как к нормальным, и я не знаю никакого способа в C #, чтобы избежать необходимости повторного извлечения потокового статического поля много раз - один раз для каждого зарегистрированного объекта IDisposable.

0 голосов
/ 23 ноября 2011

Что произойдет, если вы обернете возвращенные объекты в main в блоке using или создадите finally для удаления объектов?

Нужно ли в SomeOtherObjects реализовывать IDisposable?

...