Должны ли методы утилизации быть проверены? - PullRequest
16 голосов
/ 15 июля 2010

Я использую C #.Рекомендуется ли утилизировать методы утилизации?Если да, то почему и как следует проверять эти методы?

Ответы [ 4 ]

9 голосов
/ 16 июля 2010

Да, но это может быть сложно. В реализации Dispose обычно может произойти две вещи:

Неуправляемые ресурсы высвобождаются.

В этом случае довольно сложно проверить, что код называется, например, Marshal.Release. Возможное решение состоит в том, чтобы ввести объект, который может выполнить утилизацию, и передать ему макет во время тестирования. Что-то на этот счет:

interface ComObjectReleaser {
    public virtual Release (IntPtr obj) {
       Marshal.Release(obj);
    }
}

class ClassWithComObject : IDisposable {

    public ClassWithComObject (ComObjectReleaser releaser) {
       m_releaser = releaser;
    }

    // Create an int object
    ComObjectReleaser m_releaser;
    int obj = 1;
    IntPtr m_pointer = Marshal.GetIUnknownForObject(obj);

    public void Dispose() {
      m_releaser.Release(m_pointer);
    }
}

//Using MOQ - the best mocking framework :)))
class ClassWithComObjectTest {

    public DisposeShouldReleaseComObject() {
       var releaserMock = new Mock<ComObjectReleaser>();
       var target = new ClassWithComObject(releaserMock);
       target.Dispose();
       releaserMock.Verify(r=>r.Dispose());
    }
}

Метод Dispose других классов называется

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

Один из способов заключается в том, чтобы обернуть эти другие объекты в фиктивную оболочку, аналогично тому, что пространство имен System.Web.Abstractions делает для класса HttpContext - то есть определяет класс HttpContextBase со всеми виртуальными методами, которые просто делегируют вызовы методов действительному HttpContext класс.

Дополнительные идеи о том, как сделать что-то подобное, можно найти в System.IO.Abstractions project.

6 голосов
/ 16 июля 2010

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

Если ваш класс только создает / использует управляемые ресурсы (т.е. они реализуют IDisposable), тогда все, что вам действительно нужно, это убедиться, что метод Dispose для этих ресурсов вызывается в правильное время - если выЕсли вы используете какую-либо форму DI, вы можете ввести макет и утверждать, что Dispose был вызван.

Посмотрите на сложность ваших методов dispose - если они состоят всего из нескольких строк с возможно одним условием, спросите себяесли есть реальная польза в их модульном тестировании.

5 голосов
/ 16 июля 2010

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

Конечно, вы должны только проверятьвнешнее состояние вашего объекта.В приведенном ниже примере я сделал свойство Disposed external для присвоения мне состояния.

Рассмотрим:

internal class CanBeDisposed : IDisposable
{
    private bool disposed;
    public bool Disposed
    {
        get
        {
            if (!this.disposed)
                return this.disposed;
            throw new ObjectDisposedException("CanBeDisposed");
        }
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                //// Dispose of managed resources.
            }
            //// Dispose of unmanaged resources.
            this.disposed = true;
        }
    }
}

Итак, как я могу это проверить, так:

CanBeDisposed cbd;

using (cbd = new CanBeDisposed())
{
    Debug.Assert(!cbd.Disposed); // Best not be disposed yet.
}

try
{
    Debug.Assert(cbd.Disposed); // Expecting an exception.
}
catch (Exception ex)
{
    Debug.Assert(ex is ObjectDisposedException); // Better be the right one.
}
2 голосов
/ 16 июля 2010

Большое да - если ваша ситуация требует реализации функции Dispose - вам лучше убедиться, что она делает то, что вы думаете!

Например, у нас есть классы, которые координируют задачи базы данных (например, пакеты служб SSIS, но с SqlConnection и SqlCommand, SqlBulkCopy и т. Д.).

Если я не реализую свою утилизацию должным образом, у меня может быть незафиксированная SqlTransaction или зависшая SqlConnection. Это было бы ОЧЕНЬ плохо, если бы я запускал несколько экземпляров этих задач базы данных последовательно.

...