[.NET] 遞迴委派

[.NET] : 遞迴委派

前言 :

遞迴(Recursion) : 是一種在設計程式時會用的手法,讓函式自己呼叫自己形成迴圈,完成一些複雜的工作。
例如底下的遞迴函式,可以印出排成三角形的星星。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Recursion(0);
            Console.ReadLine();
        }

        static void Recursion(int count)
        {
            if (count > 10) return;
            count++;
            Print(count);           
            Recursion(count);
            Print(count);   
        }

        static void Print(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}

image


委派(Delegate) : 是 Command Pattern的延伸,主要是將函式做封裝。
例如在.NET裡物件的Event是使用委派來實作,多執行緒的程式也可以採用委派來做設計。
相關範例 : [Windows Forms] : 跨執行緒控制WinForm表單物件


最近在開發專案的時候,需要使用到擁有遞迴能力的委派。一開始以為是個簡單的工作,卻發現過程卻要花一些心思。
本篇文章示範,如何將遞迴跟委派做結合,讓委派也擁有遞迴的能力。
除了給自己做紀錄之外,也希望能將知識分享給有需要的開發人員。


實作 :

首先很直覺的將遞迴函式的範例,改寫成下列的程式碼。
將遞迴函式 Recursion(int count)使用匿名函式的方式封裝成委派。
但這樣的程式碼是無法通過編譯,因為在匿名函式的內部沒有辦法參考到自己。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<int> recursionDelegate = delegate(int count)
            {
                if (count > 10) return;
                count++;
                Print(count);
                recursionDelegate(count);
                Print(count);
            };
            recursionDelegate(0);
            Console.ReadLine();
        }

        static void Print(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}

image


換個解法,既然問題是沒有辦法參考到自己,那就把自己傳進去。
以這樣的思路,寫出了下列的程式碼。
但這樣的程式碼還是無法通過編譯,因為委派宣告的引數已經被改變。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<Action<int>, int> recursionDelegate = delegate(Action<int> selfDelegate, int count)
            {
                if (count > 10) return;
                count++;
                Print(count);
                selfDelegate(count);
                Print(count);
            };
            recursionDelegate(recursionDelegate, 0);
            Console.ReadLine();
        }

        static void Print(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}

image


最後,為了不被自己的委派宣告引數所限制,在傳遞自己的時候改用object類型做傳遞,要使用時再做轉型。
以這樣的思路,寫出了下列的程式碼。
這樣的程式碼通過了系統的編譯,並且執行結果也跟一開始的遞回函式範例相同。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<object, int> recursionDelegate = delegate(object selfDelegate, int count)
            {
                if (count > 10) return;
                count++;
                Print(count);
                (selfDelegate as Action<object, int>)(selfDelegate, count);
                Print(count);
            };
            recursionDelegate(recursionDelegate, 0);
            Console.ReadLine();
        }

        static void Print(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}

image
image


補充 :

感謝 Lastsecret補充說,也可以使用匿名方法的Outer變數來傳遞委派。
以這樣的思路,寫出了下列的程式碼。
這樣的程式碼通過了系統的編譯,並且執行結果也跟一開始的遞回函式範例相同。
相關資料 : 匿名方法 (C# 程式設計手冊)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            Action<int> recursionDelegate = null;

            recursionDelegate = delegate(int count)
            {
                if (count > 10) return;
                count++;
                Print(count);
                recursionDelegate(count);
                Print(count);
            };
            recursionDelegate(0);

            Console.ReadLine();
        }

        static void Print(int count)
        {
            for (int i = 0; i < count; i++)
            {
                Console.Write("*");
            }
            Console.WriteLine();
        }
    }
}
期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。