20

私は C# でデリゲートについて頭を悩ませようとしてきましたが、それらを使用する意味がわからないようです。デリゲートに関するMSDNページから少し再構築されたコードを次に示します。

using System;
using System.Collections;

namespace Delegates
{
    // Describes a book in the book list:
    public struct Book
    {
        public string Title;        // Title of the book.
        public string Author;       // Author of the book.
        public decimal Price;       // Price of the book.
        public bool Paperback;      // Is it paperback?

        public Book(string title, string author, decimal price, bool paperBack)
        {
            Title = title;
            Author = author;
            Price = price;
            Paperback = paperBack;
        }
    }

    // Declare a delegate type for processing a book:
    public delegate void ProcessBookDelegate(Book book);

    // Maintains a book database.
    public class BookDB
    {
        // List of all books in the database:
        ArrayList list = new ArrayList();

        // Add a book to the database:
        public void AddBook(string title, string author, decimal price, bool paperBack)
        {
            list.Add(new Book(title, author, price, paperBack));
        }

        // Call a passed-in delegate on each paperback book to process it:
        public void ProcessPaperbackBooksWithDelegate(ProcessBookDelegate processBook)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    processBook(b);
            }
        }

        public void ProcessPaperbackBooksWithoutDelegate(Action<Book> action)
        {
            foreach (Book b in list)
            {
                if (b.Paperback)
                    action(b);
            }
        }
    }

    class Test
    {

        // Print the title of the book.
        static void PrintTitle(Book b)
        {
            Console.WriteLine("   {0}", b.Title);
        }

        // Execution starts here.
        static void Main()
        {
            BookDB bookDB = new BookDB();
            AddBooks(bookDB);
            Console.WriteLine("Paperback Book Titles Using Delegates:");
            bookDB.ProcessPaperbackBooksWithDelegate(new ProcessBookDelegate(PrintTitle));
            Console.WriteLine("Paperback Book Titles Without Delegates:");
            bookDB.ProcessPaperbackBooksWithoutDelegate(PrintTitle);
        }

        // Initialize the book database with some test books:
        static void AddBooks(BookDB bookDB)
        {
            bookDB.AddBook("The C Programming Language",
               "Brian W. Kernighan and Dennis M. Ritchie", 19.95m, true);
            bookDB.AddBook("The Unicode Standard 2.0",
               "The Unicode Consortium", 39.95m, true);
            bookDB.AddBook("The MS-DOS Encyclopedia",
               "Ray Duncan", 129.95m, false);
            bookDB.AddBook("Dogbert's Clues for the Clueless",
               "Scott Adams", 12.00m, true);
        }
    }
}

クラスでわかるように、BookDB2 つの異なるメソッドを定義しました。

  1. 引数としてデリゲートを取るもの:ProcessPaperbackBooksWithDelegate
  2. 対応する型シグネチャのアクションを引数として取るもの:ProcessPaperbackBooksWithoutDelegate

どちらを呼び出しても同じ結果が返されます。では、デリゲートはどのような目的を解決するのでしょうか?

同じページの 2 番目の例は、さらに多くの混乱を招きます。コードは次のとおりです。

delegate void MyDelegate(string s);

static class MyClass
{
    public static void Hello(string s)
    {
        Console.WriteLine("  Hello, {0}!", s);
    }

    public static void Goodbye(string s)
    {
        Console.WriteLine("  Goodbye, {0}!", s);
    }

    public static string HelloS(string s)
    {
        return string.Format("Hello, {0}!", s);
    }

    public static string GoodbyeS(string s)
    {
        return string.Format("Goodbye, {0}!", s);
    }

    public static void Main1()
    {
        MyDelegate a, b, c, d;
        a = new MyDelegate(Hello);
        b = new MyDelegate(Goodbye);
        c = a + b;
        d = c - a;

        Console.WriteLine("Invoking delegate a:");
        a("A");
        Console.WriteLine("Invoking delegate b:");
        b("B");
        Console.WriteLine("Invoking delegate c:");
        c("C");
        Console.WriteLine("Invoking delegate d:");
        d("D");
    }

    public static void Main2()
    {
        Action<string> a = Hello;
        Action<string> b = Goodbye;
        Action<string> c = a + b;
        Action<string> d = c - a;

        Console.WriteLine("Invoking delegate a:");
        a("A");
        Console.WriteLine("Invoking delegate b:");
        b("B");
        Console.WriteLine("Invoking delegate c:");
        c("C");
        Console.WriteLine("Invoking delegate d:");
        d("D");
    }

    public static void Main3()
    {
        Func<string, string> a = HelloS;
        Func<string, string> b = GoodbyeS;
        Func<string, string> c = a + b;
        Func<string, string> d = c - a;

        Console.WriteLine("Invoking function a: " + a("A"));
        Console.WriteLine("Invoking function b: " + b("B"));
        Console.WriteLine("Invoking function c: " + c("C"));
        Console.WriteLine("Invoking function d: " + d("D"));
    }
}

Main1は既に例にあった関数です。Main2私が追加しMain3たフィドルです。

私が期待Main1Main2たように、同じ結果が得られます。

Invoking delegate a:
  Hello, A!
Invoking delegate b:
  Goodbye, B!
Invoking delegate c:
  Hello, C!
  Goodbye, C!
Invoking delegate d:
  Goodbye, D!

Main3ただし、非常に奇妙な結果が得られます。

Invoking function a: Hello, A!
Invoking function b: Goodbye, B!
Invoking function c: Goodbye, C!
Invoking function d: Goodbye, D!

+が実際に関数合成を実行していた場合、(の) 結果はMain3次のようになります。

Invoking function a: Hello, A!
Invoking function b: Goodbye, B!
Invoking function c: Hello, Goodbye, C!!
Invoking function d: //God knows what this should have been.

しかし、それが実際には従来の機能的な構成ではないことは明らかです+(実際の構成はアクションに対しても機能しないと思います)。それは、次の型シグネチャを持っていないように見えるという事実から明らかです。

(T2 -> T3) -> (T1 -> T2) -> T1 -> T3

代わりに、型シグネチャは次のようです。

(T1 -> T2) -> (T1 -> T2) -> (T1 -> T2)

+では、実際には何を-意味するのでしょうか。

余談:var a = Hello;... inを使用しようとしましMain2たが、エラーが発生しました:

test.cs(136,14): error CS0815: Cannot assign method group to an implicitly-typed
    local variable

この質問とは関係ないかもしれませんが、なぜそうできないのでしょうか? それはかなり単純なタイプの推論のようです。

4

4 に答える 4