C#のコールバックとデリゲートの違いはなんなのか!

みなさんこんにちはヒロポンです!

今回も技術系の記事を書いていきたいと思います!

今回はコールバックについて!と、デリゲートについて!

コールバックっていうのはJavaScriptでは当たり前のように使われていますが、一方のデリゲートはC#だけのものです。

とはいえこの二つ結構混同して覚えてしまう方も多いと思うので、その解説をしていきたいと思います!

Callback / Delegate / Event の関係と用途 (3用途別マッピング)
目次

C#でのコールバックとは何?

💡 イベントとデリゲートの実装比較を業務系の現場目線でまとめたのが C#のコールバックとデリゲートとイベントの違い、使い分け です。今回と合わせて読むと設計判断が固まります。

一言で言ってしまえば関数を引数とかで渡して別の関数内で実行してもらう行為!と覚えてもらえば良いと思います!

例えば下記のようなコード

namespace CallBackNDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            Say(SayHello);
            Console.ReadLine();
        }
    static void SayHello()
    {
        Console.WriteLine("HelloWorld");
    }

    static void Say(Action action)
    {
        action();
    }
}

}

これはSayHelloという関数をSay関数の中で実行しているんですね。

Say関数は引数無しの戻り値voidの関数を実行するようになっています。

これだと少しわかりずらいので別のパターンを紹介します。

    class Program
    {
        static void Main(string[] args)
        {
            Say2(GetHello);
            Console.ReadLine();
        }
    static string GetHello(string str)
    {
        return $@"Hello {str}";
    }

    static void Say2(Func<string, string> func)
    {
        var hello = func("CSharp");
        Console.WriteLine(hello);
    }
}</code></pre></div>

今回は引数stringで戻り値stringの関数を引数にしたSay2関数を用意しました。

GetHello関数はStringを引数にして、戻り値は頭にHelloと付けるだけ。

この関数を実行しているのはあくまでもSay2です。

C#のコールバックは関数を別のところで実行する行為

って事でC#のコールバックのまとめは、下記です。

  • コールバックは関数を別のところで実行する行為。

C#でのデリゲートとは?

では次にデリゲートについてですが、ものすごくミニマムでデリゲートを説明すると、デリゲートは関数の型をあらかじめ作っておいて色々できるようにする仕組みです。

下記のコードを見てください。

    public static class Delegate
    {
        public delegate string GetHelloDelegate(string str);
    public delegate void SayHelloDelegate();

    public static void Say()
    {
        var func = new SayHelloDelegate(() =&gt; Console.WriteLine(&quot;Hello World&quot;));
        func();
    }

    public static void Say2()
    {
        var func = new GetHelloDelegate(str =&gt; $@&quot;Hello {str}&quot;);
        Console.WriteLine(func(&quot;CSharp&quot;));
    }
}</code></pre></div>

GetHelloDelegateは戻り値string、引数stringの関数の型を宣言しています。

また一方のSayHelloDelegateは引数も戻り値もない関数の型を宣言しています。

で、ここから面白いのですが、型なのでNewすることができます。

このNewの引数の中で関数の宣言をすることができます。

ものすごくシンプルな話をすると関数を引数とか変数として使うときの型がDelegateってな感じで把握をしておけば、この先Delegateの便利な機能を知った時になるほど!ってなるかもしれませんね!

まとめ

  • Callbackは関数を別の場所で実行する行為
  • Delegateは関数を引数とか変数で使うときに使用する型

以上

今回のコードはGithubに上げています!

今回ブログのサンプルで書いたコードはGithubに上げています!

興味があればgit cloneしてローカルでお試しください!

GitHub
GitHub - HayashiyamaHiroshi/blog-source-demo Contribute to HayashiyamaHiroshi/blog-source-demo development by creating an account on GitHub.

💡 補足: 業務系の現場でよくハマるパターン

俺もコールバック・デリゲートを実装で何度も使いますが、業務画面で踏みがちな罠が3つほどあります。

① Action と Func を使い分けるべき場面で混乱

戻り値ありなら Func、なしなら Action、と覚えればいいんですが、Func の引数は最後が戻り値型なのが直感に反する。Func = int と string を受けて bool を返す。慣れるまで混乱します。型推論で済むケースなら lambda で書く方が読みやすいことも。 コールバックとデリゲートとイベントの違い で具体例を出してます。

② イベントの -= 忘れでメモリリーク

Form_Load で btn.Click += handler してるのに、Form 閉じる時に btn.Click -= handler を呼ばないと、Form インスタンスが GC 対象にならないことがあります。短命な Form なら問題ないですが、業務画面で何度も開き直すと地味にメモリ蓄積する。Form 間値受け渡し でも同じ問題が出るので注意。

③ 多重サブスクライブで処理が重複実行

Form_Load で毎回 += しちゃうコード、リファクタ中によく出ます。閉じて開き直すと Click ハンドラが 2回、3回と発火するバグになる。InitializeComponent 内に書くか、必ず -= を呼んでから += する型を徹底するのがセオリーです。

❓ よくある質問

Q1. Action と Func の使い分けの判断基準は?

A. 戻り値の有無で機械的に決まります。なしなら Action、ありなら Func。Func の型パラメータは「引数の型, 引数の型, ..., 戻り値の型」の順。慣れたら lambda 式で書くと型推論で省略できます。 C# Linq の Count で Func を使う実例が見れます。

Q2. delegate と event の違いは?

A. event は外部から触れる範囲を制限した delegate です。+= と -= は許可、= 代入や直接 Invoke は禁止。クラス外から「勝手にハンドラ一覧をクリア」されることを防ぐためのカプセル化。業務系ならまず event で公開するのが安全です。

Q3. Lambda 式と method group の違いは?

A. btn.Click += () => DoSomething() は lambda、 btn.Click += DoSomething は method group。後者の方が -= で外せる利点があります。lambda はインスタンスが毎回違うので -= が効かない罠あり。

Q4. デリゲート絡みのメモリリークを避けるには?

A. 長寿命オブジェクトに短寿命オブジェクトのハンドラを + 登録しない、もしくは Dispose で明示的に - する。WeakEvent パターンも選択肢ですが、業務系では「ハンドラ登録は Component の Dispose で外す」徹底の方が単純で堅い。 C# 例外処理の正解 で using/Dispose の使い分けと合わせて整理してます。

Q5. async と組み合わせるとどうなる?

A. async void は event ハンドラ専用と覚えるのが鉄則です。それ以外のコールバックで async void を使うと例外が捕まえられない。async lambda は Func を受ける delegate に渡す必要があります。C# Interface と using の使い分け の話とも繋がります。

📚 関連記事

この記事が気に入ったら
いいねしてね!

どんどんシェア待ってるぜ!!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次