2

編集 - 新しい質問

わかりました、質問をより一般的に言い換えましょう。

リフレクションを使用して、オーバーライドしている可能性のある基本クラス メソッドを実行時に動的に呼び出す方法はありますか。'base' キーワードが存在することを確認できないため、コンパイル時には使用できません。実行時に祖先のメソッドを一覧表示し、祖先のメソッドを呼び出したいと思います。

GetMethods() などを使用してみましたが、返されるのはメソッドの最も派生した実装への「ポインター」だけです。基本クラスでの実装ではありません。

バックグラウンド

比較的大きなクラス階層を持つ C# 3.0 でシステムを開発しています。これらのクラスの一部は、階層内の任意の場所に、破棄する必要のあるリソースがあり、それらはIDisposableインターフェイスを実装しています。

問題

ここで、コードのメンテナンスとリファクタリングを容易にするために、IDisposable を実装するクラスについて、先祖も IDisposable を実装している場合にbase.Dispose(bDisposing)を「自動的に」呼び出す方法を見つけたいと思います。このようにして、階層の上位にあるクラスが IDisposable の実装を開始または停止した場合、自動的に処理されます。

問題は二重です。

  • まず、先祖が IDisposable を実装しているかどうかを調べます。
  • 次に、base.Dispose(bDisposing) を条件付きで呼び出します。

最初の部分では、先祖が IDisposable を実装していることを発見し、対処することができました。

2番目の部分はトリッキーです。すべての努力にもかかわらず、派生クラスから base.Dispose(bDisposing) を呼び出すことができませんでした。私の試みはすべて失敗しました。それらはコンパイル エラーを引き起こしたか、最も派生したメソッドである間違った Dispose() メソッドを呼び出したため、永遠にループしていました。

主な問題は、 base.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 を追加しても、完全に機能します。

4

8 に答える 8

9

標準的なパターンは、基本クラスが IDisposable と非仮想 Dispose() メソッドを実装し、仮想 Dispose(bool) メソッドを実装することです。これは、使い捨てリソースを保持するクラスがオーバーライドする必要があります。それらは常に基本の Dispose(bool) メソッドを呼び出す必要があります。これは、最終的に階層の最上位クラスに連鎖します。それをオーバーライドするクラスのみが呼び出されるため、通常、チェーンは非常に短くなります。

~Class in C#: しないでください。メモリが解放される前に、ファイナライザーが少なくとも 2 つのコレクションを必要とするため、1 つを必要とするクラスはほとんどなく、誤って大きなオブジェクト グラフを維持することは非常に簡単です。オブジェクトが参照されなくなった後の最初のコレクションで、実行されるファイナライザーのキューに入れられます。これらは別の専用スレッドで実行されますファイナライザーのみを実行します (ブロックされた場合、ファイナライザーは実行されなくなり、メモリ使用量が爆発的に増加します)。ファイナライザーが実行されると、適切な世代を収集する次のコレクションが、オブジェクトとそれが参照していた他の方法で参照されていないものをすべて解放します。残念ながら、それは最初のコレクションを生き残るため、あまり頻繁に収集されない古い世代に配置されます。このため、早期かつ頻繁に処分する必要があります。

一般に、リソースの有効期間のみを管理する小さなリソース ラッパー クラスを実装し、そのクラスにファイナライザーと 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、フォームが Disposed のときに Dispose するコードを生成します。これにより、追加されたすべてのコンポーネントが Dispose されます。モバイル プロジェクトの場合、Dispose するコードcomponentsが生成されますが、コンポーネントをサーフェスにドロップしても、コンポーネントを に追加するコードは生成されませんcomponents。InitializeComponent を呼び出した後、コンストラクターでこれを自分で行う必要があります。

于 2008-09-16T23:24:31.907 に答える
3

個人的には、FxCop などでこれを処理したほうがよいと思います。IDisposable を実装するオブジェクトが作成されたときに using ステートメントを使用するかどうかを確認するルールを作成できるはずです。

オブジェクトを自動的に破棄するのは (私には) 少し汚いように思えます。

于 2008-09-16T23:09:54.547 に答える
2
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 
            }
        }
    } 

}
于 2010-09-09T23:08:53.053 に答える
2

これを行う「受け入れられた」方法はありません。失敗しないように、クリーンアップ ロジック (Dispose またはファイナライザーの内部で実行されるかどうか) をできるだけ単純にする必要があります。dispose (特にファイナライザー) 内でリフレクションを使用することは、一般的にはお勧めできません。

ファイナライザーを実装する限り、一般的には必要ありません。ファイナライザーはオブジェクトにコストを追加し、オブジェクトの状態とランタイムについて通常行うことができる仮定のほとんどが有効ではないため、正しく記述することは困難です。

Dispose パターンの詳細については、この記事を参照してください。

于 2008-10-17T09:56:27.340 に答える
0

これを試して。これはDispose()メソッドへの1行の追加であり、祖先の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);
    }
}
于 2008-09-16T23:53:37.703 に答える
0

[basetype].Invoke("Dispose"...) を使用したい場合は、デバッガーが文句を言うことなく関数呼び出しを実装できます。その後、基本型が実際に IDisposable インターフェイスを実装すると、適切な呼び出しが実行されます。

于 2008-09-16T23:14:40.527 に答える
0

[basetype].Invoke("Dispose"...) を使用したい場合は、デバッガーが文句を言うことなく関数呼び出しを実装できます。その後、基本型が実際に IDisposable インターフェイスを実装すると、適切な呼び出しが実行されます。

于 2008-09-16T23:18:31.720 に答える
0
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 である最初のクラスのみを正しく実装する責任があります。

于 2008-09-16T23:26:06.193 に答える