Delegates 的學習筆記

上課筆記:C# Advanced Topics: Take Your C# Skills to the Next Level

Delegates

  • An object that knows how to call a method (or a group of methods)
    • 在 invocationList 中可以看到要委派的 Method
    • 而因為 delegate 是繼承 System.MulticastDelegate,因此可以知道說我們要委派給誰
  • A reference to a function

用途

  • 在設計應用程式時更具有擴充性以及彈性。
  • 讓使用的人可以不需要再重新編譯原本的 Library 的情況下,還能增加需要的功能(方法)。

情境

我們現在有一個照片的處理程序,他可以套用不同的濾鏡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 相片類別
public class Photo
{
public static Photo Load(string path)
{
return new Photo();
}

public void Save()
{
}
}
// 相片濾鏡類別
public class PhotoFilters
{
public void ApplyBrightness(Photo photo)
{
Console.WriteLine("Apply Brightness !!!");
}

public void ApplyContrast(Photo photo)
{
Console.WriteLine("Apply Contrast !!!");
}

public void Resize(Photo photo)
{
Console.WriteLine("Resize Photo !!!");
}
}
// 處理相片程序的類別
public class PhotoProcessor
{
public void Process(string path)
{
var photo = Photo.Load(path);

var filters = new PhotoFilters();
filters.ApplyBrightness(photo);
filters.ApplyContrast(photo);
filters.Resize(photo);

photo.Save();
}
}
1
2
3
4
5
6
7
8
9
// 來處理一下照片
internal class Program
{
private static void Main(string[] args)
{
var process = new PhotoProcessor();
process.Process("photo.jpg");
}
}

上述的程式碼,從設計上來看 PhotoProcessor 裡面在套用濾鏡 PhotoFilters,它不具有太大的可動性(彈性),也就是說我如果要在同一個應用程式使用不同的濾鏡,我就必須寫另一個 PhotoProcessor,更甚者,重新修改原本的 PhotoProcessor,那我們可以怎麼透過 delegate 來讓他具有可動性(彈性)呢? 在 PhotoProcessor 中動手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 修改 相片程序的類別
public class PhotoProcessor
{
// 增加下面這行。
// 在前面有提到,宣告 delegate 的時候,我們需要定義要委派的簽章,情境中,我們需要讓
// 在使用 PhotoProcessor 的時候,可以自由的選擇要使用的濾鏡。
// 也就是說,在使用的時候,我們會利用這個 delegate 指向我們要用的 method 或者是
// a group of method
public delegate void PhotoFilterHandler(Photo photo);

public void Process(string path, PhotoFilterHandler filterHandler)
{
var photo = Photo.Load(path);

//var filters = new PhotoFilters();
//filters.ApplyBrightness(photo);
//filters.ApplyContrast(photo);
//filters.Resize(photo);
// 增加下面這行,並移除上面四行。
// 代表編譯的時候,我們並不知道它會套用哪個 filter
// 完全由用的人決定。
filterHandler(photo);

photo.Save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 來處理一下照片
internal class Program
{
private static void Main(string[] args)
{
var process = new PhotoProcessor();
// 增加下面兩行
var filters = new PhotoFilters();
PhotoProcessor.PhotoFilterHandler filterHandler = filters.ApplyBrightness;
// 將 filterHandler 傳回 Process
// 讓 Process 知道有哪些 filter 要處理
// 這就是讓 要用的人決定要使用哪些 filter
process.Process("photo.jpg", filterHandler);
}
}

那如果要在加另外一個 filter 呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 來處理一下照片
internal class Program
{
private static void Main(string[] args)
{
var process = new PhotoProcessor();
var filters = new PhotoFilters();
PhotoProcessor.PhotoFilterHandler filterHandler = filters.ApplyBrightness;
// 透過 += 或是 -= 來新增或者移除你的 delegate
filterHandler += filters.Resize;
process.Process("photo.jpg", filterHandler);
}
}

甚至是,我不要用 PhotoFilters 內的 filter 也行,我自己來新建一個

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 來處理一下照片
internal class Program
{
private static void Main(string[] args)
{
var process = new PhotoProcessor();
var filters = new PhotoFilters();
PhotoProcessor.PhotoFilterHandler filterHandler = filters.ApplyBrightness;
filterHandler += filters.Resize;
// 一樣加到 filterHandler 上,讓他送進 Process v( ̄︶ ̄)y
filterHandler += RemoveRedEyeFilter;
process.Process("photo.jpg", filterHandler);
}

// 新增自己的 Filter
static void RemoveRedEyeFilter(Photo photo)
{
Console.WriteLine("Apply Remove Red Eye Filter !!!");
} }

缺點

  • 在 Delegate 中,方法的簽章要與 宣告 delegate 的簽章一樣,因次遇到需要傳入多種不同或是型別的參數時,勢必要寫出一堆 delegate。
    • 因此在 C#2.0 開始,在 .Net Framework 內建了 Action、Func。
    • 上述提到的 Action、Func,都是可以使用 Generic 的

在上述缺點有提到兩樣 Generic Delegate:Action 跟 Func

若把 Action 用在 上面的情境,可以這樣使用

1
2
3
4
5
6
7
8
9
10
11
12
13
// 修改 相片程序的類別
public class PhotoProcessor
{
// 將原本的 **PhotoFilterHandler** 移除,換成 Action<Photo>
public void Process(string path, Action<Photo> filterHandler)
{
var photo = Photo.Load(path);

filterHandler(photo);

photo.Save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 來處理一下照片
internal class Program
{
private static void Main(string[] args)
{
var process = new PhotoProcessor();
var filters = new PhotoFilters();
// 將原本的 **PhotoFilterHandler** 移除,換成 Action<Photo>
Action<Photo> filterHandler = filters.ApplyBrightness;
process.Process("photo.jpg", filterHandler);
}
}

那麼,跟 Delegate 有一樣的功效的解法是什麼? Interface

Interfaces or Delegates

那怎麼抉擇? 這個因人而異啦~ 每個人都有自己的想法以及用法
不過在 微軟的 MSDN 有寫到,可以參考一下,這部份還沒有整理….

See also