Вопрос по dispose, interop, c# – Как реализовать шаблон dispose в c # при переносе COM-объекта Interop?

5

Мой класс содержит объект из Interop и вызывает для него метод, который заставляет его распределять вещи. Он также предоставляет метод для освобождения этого материала, поэтому я ожидаю, что я должен вызвать его в Dispose ():

class MyClass : IDisposable
{
    private DllName.ComClassName comInstance;

    void SomeMethod()
    {
        comInstance = new DllName.ComClassName();
        comInstance.AllocStuff();
    }

    public void Dispose()
    {
        comInstance.FreeThatStuff();
    }
}

Теперь я должен расширить все это, чтобы следовать шаблону Dispose. У меня нет других одноразовых или неуправляемых ресурсов для освобождения, поэтому, если предположить, что comInstance является управляемым (разве не то, что делает Interop, а неуправляемый превращается в управляемый?), Я думаю, что шаблон распадается на:

public void Dispose()
{
    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }
}

Какие утечки, если я явно не вызову Dispose () для экземпляров MyClass, что сделает шаблон Dispose некорректным? Значит ли это, что comInstance должен быть неуправляемым, а шаблон -

public void Dispose()
{
    DisposeComInstance();
    GC.SuppressFinalize(this);
}

~MyClass()
{
    DisposeComInstance();
}

private void DisposeComInstance()
{
    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }
}

EDIT:

To avoid cluttering my class with the full pattern, could I just seal my class? How do I know ComClassName (and in general any class) is unmanaged?

Ваш Ответ

3   ответа
3

В конечном итоге вы хотите такой шаблон:

public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

~MyClass()
{
    Dispose(false);
}

private void Dispose(bool disposing)
{
    if (disposing)
    {
        // Dispose of disposable objects here

    }

    // Other unmanaged cleanup here which will be called by the finalizer
    if (comInstance != null)
    {
         comInstance.FreeStuff();
         comInstance = null;
    }

    // Call base dispose if inheriting from IDisposable class.
    base.Dispose(true);
}

Для большой статьи о том, почемуРеализация IDisposable и шаблон Dispose правильно.

+1, правильно реализовано ;-D
-1, вам нужно переместить неуправляемые объекты за пределы блока if (утилизации). Неуправляемый всегда должен быть убран.
Г! Вы правы, я скопировал его из некоторого моего кода, в котором использовались компоненты, в которых был неуправляемый код, но реализован метод dispose, и отредактированный в примере OP ... вставьте для потери. Исправлена.
3

Во-первых, я согласен с теми, кто предлагает использовать финализатор в качестве резервной копии, но стараюсь не вызывать его когда-либо явно, вызывая myClass.Dispose или через «использование».

например

var myClass = new MyClass()
try
{
   //do stuff
}
finally
{
   myClass.Dispose();
}

или же

using (var myClass = new MyClass())
{
   //do stuff
}

Marshall.ReleaseComObject

Если вы используете много COM-объектов, я также предлагаю вам использовать Mashall.ReleaseComObject (comObj) для явной очистки ссылок на RCW.

Итак, код, подобный этому, предлагается в другом месте:

if (comInstance != null)
{
    comInstance.FreeStuff();
    comInstance = null;
}

станет:

if (comInstance != null)
{
    comInstance.FreeStuff();

    int count = Marshall.ReleaseComObject(comInstance);
    if (count != 0)
    {
            Debug.Assert(false, "comInstance count = " + count);
            Marshal.FinalReleaseComObject(comInstance);
    }

    comInstance = null;
}

Хотя проверка возвращаемого значения ReleaseComObject () не является строго необходимой, я хотел бы проверить, чтобы убедиться, что все увеличивается / уменьшается, как и ожидалось.

The 2-dot rule

Если вы решите использовать это, то следует помнить, что для правильного освобождения ваших COM-объектов может потребоваться рефакторинг некоторого кода. Одним из них является то, что я называю правилом из двух точек. Любая строка, использующая COM-объекты, содержащие 2 точки, требует пристального внимания. Например,

var name = myComObject.Address.Name;

В этом выражении мы получаем ссылку на объект Address COM, увеличивая его счетчик ссылок RCW, но у нас нет возможности вызвать ReleaseComObject. Лучший способ сделать это - разбить его (попробуйте ... для ясности опущено):

var address = myComObject.Address;
var name = address.Name;
MyReleaseComObject(address);  

где MyReleaseComObject - это служебный метод, заключающий в себе мой счетчик и метод FinalReleaseComObject () сверху.

Будьте осторожны сFinalReleaseComObject, В зависимости от того, где вы получаете ком-объект, это может быть опасно. Вы можете закончить тем, что два com-объекта фактически одинаковыFinalRelaseComObject на самом деле убивает обоих. Если вы затем используете другой объект Com, вы получите неожиданные ошибки, которые не всегда легко найти.
2

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

public void Dispose()
{
    this.Dispose(true);
    GC.SuppressFinalize(this);
}

~MyClass()
{
    this.Dispose(false);
}

protected virtual void Dispose(bool disposing)
{
    // if (disposing)
    // {
    //      // Managed
    // }

    if (comInstance != null)
    {
        comInstance.FreeStuff();
        comInstance = null;
    }

    // base.Dispose(disposing) if required
}
Мета-мета-комментарий - также важно, чтобы вы никогда не получали блокировки или не использовали блокировки во время неуправляемой очистки.
Мета-комментарий: убедитесь, что ваш Dispose (bool) может терпеть вызовы много раз подряд. Также постарайтесь минимизировать вероятность возникновения исключения (не всегда возможно).

Похожие вопросы