Вызов base.Dispose () автоматически из производных классов

Изменить - новый вопрос

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

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

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

Фон

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

Проблема

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

Проблема состоит в двух аспектах.

  • Во-первых, выясняем, реализуют ли какие-либо предки IDisposable.
  • Во-вторых, условно вызывая base.Dispose (bDisposing).

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

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

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

Мое решение

Моим лучшим шансом было использовать условный код в закомментированном коде. Изменение иерархии 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, по-прежнему работает отлично.

Не используйте финализаторы C# (например, ~ MyDerivedClass ()), они недетерминированы и могут иногда не запускаться

Raphael 10.09.2010 03:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
12 017
8

Ответы 8

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

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

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

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

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

Финализаторы, пишущиеся ~ Class на 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, когда форма Disposed - он, в свою очередь, удаляет все добавленные компоненты. Для мобильных проектов создается код Dispose components, но при падении компонента на поверхность код для добавления его в components не создается.. Вы должны сделать это самостоятельно в своем конструкторе после вызова InitializeComponent.

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

Chris Charabaruk 22.09.2008 09:20

Не существует финализатора по умолчанию. Если вы его не создадите, он не существует и не будет завершен.

Mike Dimmick 22.09.2008 16:59

Классы, реализующие IDisposable, как правило, должен реализуют финализатор вместе со стандартным одноразовым шаблоном, который включает вызов GC.SuppressFinalize (this) в «public Dispose ()», чтобы избежать затрат на финализатор при правильной утилизации вызывающей стороны.

Joe 17.10.2008 14:13

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

supercat 16.12.2012 03:38

@Joe Многие методы Dispose() существуют только для вызова методов Dispose() переменных-членов. Такие классы должны нет реализовывать финализатор; только классы, которые напрямую владеют неуправляемыми ресурсами, должны когда-либо реализовывать финализаторы. Я думаю, что лучший образец Dispose() находится в Windows.Forms: protected virtual Dispose(bool disposing) { if (disposing) {member.Dispose();} idempotentUnmanagedResourceFree(); } public void Dispose() { Dispose(true); }. Вызывайте Dispose(false) только из финализатора и пишите такой финализатор только в том случае, если этот класс напрямую владеет неподключенными ресурсами.

binki 07.02.2014 22:28

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.

Попробуй это. Это однострочное дополнение к методу 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);
    }
}

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

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

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

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 
            }
        }
    } 

}

Другие вопросы по теме