Вызов base.Dispose () автоматически из производных классов - PullRequest
2 голосов
/ 17 сентября 2008

Редактировать - Новый вопрос

Хорошо, давайте перефразируем вопрос более обобщенно.

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

Я пытался использовать GetMethods () и тому подобное, но все, что они возвращают, это «указатели» на наиболее производную реализацию метода. Не реализация в базовом классе.

Фон

Мы разрабатываем систему на C # 3.0 с относительно большой иерархией классов. Некоторые из этих классов в любой части иерархии имеют ресурсы, которые необходимо утилизируются те, которые реализуют интерфейс IDisposable .

Проблема

Теперь, чтобы облегчить поддержку и рефакторинг кода, я хотел бы найти способ для классов, реализующих IDisposable, для "автоматического" вызова base.Dispose (bDisposing) , если какие-либо предки также реализуют IDisposable. Таким образом, если какой-то класс выше в иерархии начинает реализовывать или прекращает реализацию IDisposable, о котором позаботятся автоматически.

Вопрос в два раза.

  • Во-первых, обнаружение, реализует ли какой-либо предок IDisposable.
  • Во-вторых, вызывая базу. Уничтожить (bDisposing) условно.

В первой части, касающейся предков, реализующих IDisposable, мне удалось разобраться.

Вторая часть хитрая. Несмотря на все мои усилия, я не смог вызвать base.Dispose (bDisposing) из производного класса. Все мои попытки провалились. Они либо вызвали ошибки компиляции или вызванный неправильный метод Dispose (), который является наиболее производным, таким образом, цикл зацикливается навсегда.

Основная проблема в том, что вы не можете на самом деле ссылаться на base.Dispose () непосредственно в вашем коде, если нет такой вещи как Предок, реализующий его ( Напоминаем, что, возможно, еще не было предков, еще реализующих IDisposable, но я хочу, чтобы производный код был готов, когда и если таковой будет что-то случится в будущем ). Это оставляет нам механизмы Reflection , но я не нашел подходящего способа сделать это. Наш код довольно заполнен продвинутые техники отражения, и я думаю, что не пропустил ничего очевидного.

Мое решение

Моим лучшим вариантом было использование условного кода в закомментированном коде. Изменение иерархии IDisposable приведет к поломке сборки (если не существует IDisposable предка) или выведите исключение (если есть IDisposable предки, но base.Dispose не вызывается).

Вот код, который я публикую, чтобы показать вам, как выглядит мой метод Dispose (bDisposing). Я ставлю этот код в конце всех Dispose () методы по всей иерархии. Любые новые классы создаются из шаблонов, которые также включают этот код.

public class MyOtherClassBase
{
    // ...
}


public class MyDerivedClass : MyOtherClassBase, ICalibrable
{

    private bool m_bDisposed = false;

    ~MyDerivedClass()
    {
        Dispose(false);
    }

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

    protected virtual void Dispose(bool bDisposing)
    {
        if (!m_bDisposed) {
            if (bDisposing) {
                // Dispose managed resources
            }
            // Dispose unmanaged resources
        }
        m_bDisposed = true;

        Type baseType = typeof(MyDerivedClass).BaseType;
        if (baseType != null) {
            if (baseType.GetInterface("IDisposable") != null) {
                // If you have no ancestors implementing base.Dispose(...), comment
                // the following line AND uncomment the throw. 
                //
                // This way, if any of your ancestors decide one day to implement 
                // IDisposable you will know about it right away and proceed to 
                // uncomment the base.Dispose(...) in addition to commenting the throw.
                //base.Dispose(bDisposing);
                throw new ApplicationException("Ancestor base.Dispose(...) not called - " 
                                               + baseType.ToString());
            }
        }
    }
}

Итак, я спрашиваю, есть ли способ вызвать base.Dispose () вместо этого автоматически / условно?

Больше фона

В приложении есть еще один механизм, в котором все объекты зарегистрированы главным классом. Класс проверяет, реализуют ли они IDisposable. Если это так, они утилизируются надлежащим образом приложением. Это позволяет избежать использования кода для использования классов вызывая Dispose () вокруг себя. Таким образом, добавление IDisposable в класс, у которого нет предковой истории IDisposable, по-прежнему работает отлично.

Ответы [ 8 ]

9 голосов
/ 17 сентября 2008

Стандартный шаблон для вашего базового класса для реализации IDisposable и невиртового метода Dispose (), а также для реализации виртуального метода Dispose (bool), который должны переопределять те классы, которые содержат одноразовые ресурсы. Они должны всегда вызывать свой базовый метод Dispose (bool), который в конечном итоге будет связан с верхним классом в иерархии. Будут вызваны только те классы, которые переопределяют его, поэтому цепочка обычно довольно короткая.

Финализаторы, пишется ~ Класс в C #: не надо. Очень немногим классам понадобится один, и очень легко случайно хранить большие графы объектов, потому что финализаторам требуется по крайней мере две коллекции до освобождения памяти. В первой коллекции после того, как на объект больше не ссылаются, он помещается в очередь финализаторов для запуска. Они запускаются в отдельном выделенном потоке , который запускает только финализаторы (если он заблокирован, финализаторы больше не запускаются и использование вашей памяти взрывается). Как только финализатор запустится, следующая коллекция, которая соберет соответствующее поколение, освободит объект и все остальное, на которое он ссылался, на которые иначе не ссылаются. К сожалению, поскольку он сохранился до первой коллекции, он будет помещен в старшее поколение, которое собирается реже. По этой причине вы должны утилизировать рано и часто.

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

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

ПРЕДУПРЕЖДЕНИЕ для разработчиков Windows Mobile (VS2005 и 2008, .NET Compact Framework 2.0 и 3.5): множество неконтролируемых элементов, которые вы перетаскиваете на поверхность конструктора, например, строки меню, таймеры, HardwareButtons, производные от System.ComponentModel.Component, который реализует финализатор. Для настольных проектов Visual Studio добавляет компоненты в System.ComponentModel.Container с именем components, который генерирует код для удаления при удалении формы - он, в свою очередь, удаляет все добавленные компоненты. Для мобильных проектов генерируется код Dispose components, , но падение компонента на поверхность не создает код для добавления его в components. Вы должны сделать это самостоятельно в своем конструкторе после вызова InitializeComponent.

3 голосов
/ 17 сентября 2008

Лично я думаю, что вам лучше справиться с этим с помощью FxCop. Вы должны быть в состоянии написать правило, которое проверяет, чтобы увидеть, если при создании объекта, реализующего IDisposable, вы используете оператор using.

Мне кажется немного грязным (автоматически) распоряжаться объектом.

2 голосов
/ 10 сентября 2010
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestDisposeInheritance
{
    class Program
    {
        static void Main(string[] args)
        {
            classC c = new classC();
            c.Dispose();
        }
    }

    class classA: IDisposable 
    { 
        private bool m_bDisposed;
        protected virtual void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose A"); 
                }
                // Dispose unmanaged resources 
            }
        }
        public void Dispose() 
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing A"); 
        } 
    } 

    class classB : classA, IDisposable 
    {
        private bool m_bDisposed;
        public void Dispose()
        {
            Dispose(true);
            base.Dispose();
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing B");
        }

        protected override void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose B");
                }
                // Dispose unmanaged resources 
            }
        }
    } 

    class classC : classB, IDisposable 
    {
        private bool m_bDisposed;
        public void Dispose() 
        {
            Dispose(true);
            base.Dispose();
            GC.SuppressFinalize(this);
            Console.WriteLine("Disposing C");             
        }
        protected override void Dispose(bool bDisposing)
        {
            if (!m_bDisposed)
            {
                if (bDisposing)
                {
                    // Dispose managed resources
                    Console.WriteLine("Dispose C");             
                }
                // Dispose unmanaged resources 
            }
        }
    } 

}
2 голосов
/ 17 октября 2008

Нет «принятого» способа сделать это. Вы действительно хотите сделать свою логику очистки (работает ли она внутри Dispose или финализатора) максимально простой, чтобы она не потерпела неудачу. Использование отражения внутри диспозиции (и особенно финализатора), как правило, плохая идея.

Что касается реализации финализаторов, в общем, вам не нужно. Финализаторы добавляют стоимость к вашему объекту, и ее сложно написать правильно, так как большинство предположений, которые вы обычно делаете о состоянии объекта и времени выполнения, недопустимы.

См. статью для получения дополнительной информации о шаблоне Dispose.

0 голосов
/ 17 сентября 2008

Попробуй это. Это однострочное дополнение к методу Dispose (), которое вызывает удаление предка, если оно существует. (Обратите внимание, что Dispose(bool) не является членом IDisposable)

// Disposal Helper Functions
public static class Disposing
{
    // Executes IDisposable.Dispose() if it exists.
    public static void DisposeSuperclass(object o)
    {
        Type baseType = o.GetType().BaseType;
        bool superclassIsDisposable = typeof(IDisposable).IsAssignableFrom(baseType);
        if (superclassIsDisposable)
        {
            System.Reflection.MethodInfo baseDispose = baseType.GetMethod("Dispose", new Type[] { });
            baseDispose.Invoke(o, null);
        }
    }
}

class classA: IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing A");
    }
}

class classB : classA, IDisposable
{
}

class classC : classB, IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing C");
        Disposing.DisposeSuperclass(this);
    }
}
0 голосов
/ 17 сентября 2008
public class MyVeryBaseClass {
    protected void RealDispose(bool isDisposing) {
        IDisposable tryme = this as IDisposable;
        if (tryme != null) { // we implement IDisposable
            this.Dispose();
            base.RealDispose(isDisposing);
        }
    }
}
public class FirstChild : MyVeryBaseClasee {
    //non-disposable
}
public class SecondChild : FirstChild, IDisposable {
    ~SecondChild() {
        Dispose(false);
    }
    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
        base.RealDispose(true);
    }
    protected virtual void Dispose(bool bDisposing) {
        if (!m_bDisposed) {
            if (bDisposing) {
            }// Dispose managed resources
        } // Dispose unmanaged resources
    }
}

Таким образом, вы обязаны правильно реализовать только первый класс, который является IDisposable.

0 голосов
/ 17 сентября 2008

Если вы хотите использовать [basetype] .Invoke («Dispose» ...), тогда вы можете реализовать вызов функции без жалоб отладчика. Затем, когда базовый тип фактически реализует интерфейс IDisposable, он выполнит правильный вызов.

0 голосов
/ 17 сентября 2008

Если вы хотите использовать [basetype] .Invoke («Dispose» ...), тогда вы можете реализовать вызов функции без жалоб отладчика. Затем, когда базовый тип фактически реализует интерфейс IDisposable, он выполнит правильный вызов.

...