質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.85%

C#でリフレクションを使って強制的にFinalizeを呼び出してオブジェクトを削除するとどうなるか

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,199

Takym

score 19

興味本位で質問します。
題名通り、以下の様にリフレクションを使って強制的にFinalizeを呼び出したらどうなるでしょうか?

var mi = obj.GetType().GetMethod(
    "Finalize",
    BindingFlags.NonPublic |
    BindingFlags.InvokeMethod |
    BindingFlags.Instance);
mi.Invoke(obj, null);
GC.SuppressFinalize(obj);


例えば以下の様なプログラムを実行してもエラーが発生しませんでした。

using System;
using System.Reflection;

namespace Test
{
    /// <summary>
    ///  このアプリケーションのメインクラスです。
    /// </summary>
    public static class Program
    {
        /// <summary>
        ///  このアプリケーションのエントリポイントです。
        /// </summary>
        /// <param name="args">コマンドライン引数を取得します。</param>
        /// <returns>OSに実行結果を表す数値を返します。</returns>
        [STAThread()]
        public static int Main(params string[] args)
        {
            // 普通のオブジェクトを削除する
            object a = new object();
            Console.WriteLine("Delete前(a):" + a);
            Delete(a);
            GC.Collect(); // これを呼び出してメモリから完全に削除する
            Console.WriteLine("Delete後(a):" + a);

            // 簡易的なオブジェクトを削除する
            Data b = new Data("Hello, World!!");
            Console.WriteLine("Delete前(b.Text):" + b.Text);
            Delete(b);
            GC.Collect(); // これを呼び出してメモリから完全に削除する
            Console.WriteLine("Delete後(b.Text):" + b.Text);

            // バイト配列を削除する
            byte[] c = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Console.WriteLine("Delete前(c):" + string.Join(",", c));
            Delete(c);
            GC.Collect(); // これを呼び出してメモリから完全に削除する
            Console.WriteLine("Delete後(c):" + string.Join(",", c));

            // バイト配列を保持したオブジェクトを削除する
            Data2 d = new Data2(9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
            Console.WriteLine("Delete前(d.Values):" + string.Join(",", d.Values));
            Delete(d);
            GC.Collect(); // これを呼び出してメモリから完全に削除する
            Console.WriteLine("Delete後(d.Values):" + string.Join(",", d.Values));

            Console.ReadKey(true);

            return 0;
        }

        /// <summary>
        ///  指定したオブジェクトのデストラクタを呼び出して削除します。
        /// </summary>
        /// <param name="obj">削除するオブジェクトです。</param>
        public static void Delete(object obj)
        {
            var mi = obj.GetType().GetMethod(
                "Finalize",
                BindingFlags.NonPublic |
                BindingFlags.InvokeMethod |
                BindingFlags.Instance);
            mi.Invoke(obj, null);
            GC.SuppressFinalize(obj);
        }

        /// <summary>
        ///  文字列データを保持するクラスです。
        /// </summary>
        public class Data
        {
            /// <summary>
            ///  文字列データです。
            /// </summary>
            public string Text { get; set; }

            /// <summary>
            ///  型'<see cref="Test.Program.Data"/>'の新しいインスタンスを生成します。
            /// </summary>
            /// <param name="str">このクラスに保存する文字列です。</param>
            public Data(string str) {
                this.Text = str;
            }

            /// <summary>
            ///  現在のオブジェクトのリソースを解放します。
            /// </summary>
            ~Data() {
                this.Text = null;
                Console.WriteLine("デストラクタが呼び出されました。");
            }
        }

        /// <summary>
        ///  バイト配列を保持するクラスです。
        /// </summary>
        public class Data2
        {
            /// <summary>
            ///  バイト配列です。
            /// </summary>
            public byte[] Values { get; set; }

            /// <summary>
            ///  型'<see cref="Test.Program.Data2"/>'の新しいインスタンスを生成します。
            /// </summary>
            /// <param name="values">このクラスに保存するバイト配列です。</param>
            public Data2(params byte[] values) {
                this.Values = values;
            }
        }
    }
}


コンソールには以下の様に表示されました。

Delete前(a):System.Object
Delete後(a):System.Object
Delete前(b.Text):Hello, World!!
デストラクタが呼び出されました。
Delete後(b.Text):
Delete前(c):0,1,2,3,4,5,6,7,8,9
Delete後(c):0,1,2,3,4,5,6,7,8,9
Delete前(d.Values):9,8,7,6,5,4,3,2,1,0
Delete後(d.Values):9,8,7,6,5,4,3,2,1,0


強制的にFinalizeを呼び出す事により何か問題が発生するのでしょうか?それとも発生しないのでしょうか?
ご回答お待ちしています。

環境

OS: Microsoft Windows 10 Creators Update
ランタイム: .NET Framework v4.7

補足

ここで得られた回答の一部を自分のブログに投稿してもよろしいでしょうか?
引用元は必ず明記します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • yohhoy

    2017/10/13 13:18

    補足に対して → https://teratail.com/legal および一般的な引用の範囲内でよいのではないでしょうか。

    キャンセル

回答 3

checkベストアンサー

+4

間違っているならご指摘お願いします。

これはリフレクションを使用するしないの違いはないのではないかと思います。

// 普通のオブジェクトを削除する
object a = new object();
Console.WriteLine("Delete前(a):" + a);
Delete(a);
GC.Collect(); // 1
Console.WriteLine("Delete後(a):" + a); // 2
a = null;  // 3
GC.Collect(); // 4


1の段階ではaのインスタンスは参照されているのでGC対象にならず存在している。
なので、2でアクセスできている。
3でインスタンスの参照がなくなるのでGC対象となり、4でメモリから消える

DataクラスのようにFinalize時に自分の使用している資源を解放するようなクラスの場合は
Finalize後にアクセスすることによりエラーが発生する可能性があると思います。
(サンプルでは文字列の削除のみなので問題は出ていませんが)

その他の場合、インスタンスの参照がなくなるまではエラーが出ないとは思いますが
Finalize後にそのインスタンスを使用するのはやってはいけないですよね。

要はリフレクションによるFinalizeの呼び出しが問題なのではなく、Finalize後にそのインスタンスを利用するのが問題かと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/10/13 10:42

    そうですね。Deleteメソッドを実行した後に、削除したオブジェクトにアクセスする事自体間違っていますね。

    キャンセル

  • 2017/10/28 08:38 編集

    ブログ(https://takymsystems.blog.fc2.com/blog-entry-54.html)の引用に使用したためベストアンサーに選びました。
    他の方の回答も素晴らしかったです。
    回答していただきありがとうございました。

    キャンセル

+2

他の方の言う通り単なるメソッド(イベント)なので、実行して問題が起こることはないと思います。デストラクタ実行前に暗黙で呼ばれるというだけです。

最後にやるべき処理をFinalizeに書く半面、それ以外のタイミングにやったらまずいことを書く印象があります。リソースの開放などがそれにあたる処理です。

「(マナーとして)やるべきではない」という点を他の方の意見に追加したいと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/10/13 10:45

    >「(マナーとして)やるべきではない」
    言われてから気づきました。
    確かに、リフレクションは普段は実行しませんよね。

    キャンセル

+1

まず最初にお断りとして、きちんと検証したうえでの回答ではありません。

リフレクションを使って強制的にFinalizeを呼び出したらどうなるでしょうか?

Finalizeはオブジェクトの削除時に呼ばれるメソッドではありますが、Finalizeが呼ばれたらからといってオブジェクトが削除されるわけではないかと思います。

// 普通のオブジェクトを削除する
object a = new object();
Console.WriteLine("Delete前(a):" + a);
Delete(a);
GC.Collect(); // これを呼び出してメモリから完全に削除する
Console.WriteLine("Delete後(a):" + a);

上記でGC.Collect();を実行していますが、このタイミングではaはまだオブジェクトを参照しているため、該当のオブジェクトはGCの対象とはならないものと思われます。
そのため、GC実行後もaは問題なく参照が可能となっている認識です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/10/13 10:41

    言われてみればそうですね。Finalizeはメソッドであって、GCによりオブジェクトが破棄される時に呼び出されるだけですね。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.85%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • C#に関する質問
  • C#でリフレクションを使って強制的にFinalizeを呼び出してオブジェクトを削除するとどうなるか