Professional Documents
Culture Documents
Chapter 15
委派與索引子
______________________________________________
委派
索引子
委派的作用類似於 C 和 C++中的函式指標,並且可以將函式
當成參數傳遞。索引子主要使用於當類別中的資料成員是集
合或是陣列形式時,利用索引子的特性,可以把類別所宣告
的物件以陣列索引的方式進行存取,藉此存取此陣列形式的
資料成員。
15-1 委派
委
派(delegate)的作用,類似於 C 和 C++中的函式指標,並且可以將函式
當成參數傳遞。可以把委派想成是房屋仲介公司,例如要買房子,並不需
要親自到處去找要出售的房屋,而是去房屋仲介公司詢問,他們會幫您找到合適
的房屋並處理細節;但是最後還要自己去簽定買賣契約等相關的事情。
委派就如同房屋仲介的腳色,房屋就如同是函式,將不同的函式指定給委派就如
同房屋仲介有很多不同的待賣房子;執行委派就如同找到了合適的房子;最後自
己去簽訂契約等細節,就如同函式的內容還是需要撰寫實際的程式碼。
使用委派的最大好處,就是可以簡化判斷敘述的複雜度;例如:有 10 個不同的
情形要執行 10 種不同的函式,透過委派則可以簡化這樣的判斷敘述。但是為了
要達到這樣的功能,如何設計委派則變得相對的重要。
範例 1:認識委派
建立㇐個委派函式,顯示字串:"Hello, 早安, 瑪莉"以及"Hello, 早安,
約翰"。
㇐、解說
要完成委派需要有 4 個步驟:1.建立需要被委派的函式、2. 建立委派、3.
宣告委派型別的變數、4. 將函式設定給委派變數。
建立被委派的函式
需要被委派的函式與㇐般的函式無異;例如:函式 HiMary()用於回傳 1 個
組合後的字串。HiMary()函式有 1 個字串參數 str;第 3 行則回傳"Hello,
早安, "與參數 str 組合後的字串。
建立委派
宣告委派的形式如下所示。最前面冠以關鍵字"delegate",接著是函式
宣告。
宣告委派型別的變數
如下所示,建立 Dgate 型別的變數 Delfunc。
Dgate DelFunc;
將函式設定給委派變數
如下所示,將 HiMary()函式設定給委派變數 Delfunc。
DelFunc = HiMary;
二、執行結果
下圖左為原始畫面。按下「button1」按鈕後,輸出結果如下圖右所示。
三、表單設計
請在表單上放置如下表所列之控制項。
控制項 屬性 設定值
Button (Name) button1
Text button1
TextBox (Name) textbox1
Multiline True
四、撰寫程式碼
1. 建立委派函式。程式碼第 15 行,在全域變數區宣告 Dgate()委派,回
傳值型別為 string,並帶有㇐個字串參數。
完整程式列表
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WindowsFormsApp1
12 {
13 public partial class Form1 : Form
14 {
15 delegate string Dgate(string str);
16
17 public Form1()
18 {
19 InitializeComponent();
20 }
21
22 public string HiMary(string str)
23 {
24 return "Hello, 早安, " + str + "\r\n";
25 }
26
27 public string HiJohn(string str)
28 {
29 return "Hello, 午安, " + str + "\r\n";
30 }
31
32 private void Button1_Click(object sender, EventArgs e)
33 {
34 Dgate DelFunc;
35
36 DelFunc = HiMary;
37 textBox1.AppendText(DelFunc("瑪莉"));
38
39 DelFunc = HiJohn;
40 textBox1.AppendText(DelFunc("約翰"));
41 }
42 }
43 }
範例 2:委派-多點傳送
寫㇐程式,模擬文具用品與浴室用品特價商品剩餘的數量。例如,先顯示:"今
日特價商品:雙層鉛筆盒",接著再顯示:"今日特價商品:只剩下 25 個"。
其中,字串"今日特價商品:"、特價商品名稱、剩餘數量以多點重算之委派函
式處理。
㇐、解說
使用相同委派形式所宣告的委派變數,可以使用"+"、"-"運算子將委派
變數進行組合,此方式稱為多重傳送的委派(Multicast delegate)
。
allDel = del1+del2;
所以對於委派而言,運算子"+"更像是定義函式執行的先後執行關係,並
依照順序執行。接著,再接續如下 2 個敘述:
1. allDel -= del2;
2. allDel += del3;
二、執行結果
下圖左為原始畫面。點選「文具用品」後按下「Go」按鈕後,輸出結果如下
圖中所示。點選「浴室用品」後按下「Go」按鈕後,輸出結果如下圖右所示。
三、表單設計
請在表單上放置如下表所列之控制項。
控制項 屬性 設定值
RadioButton (Name) radioButton1
控制項 屬性 設定值
Text 文具用品
Checked True
RadioButton (Name) radioButton2
Text 浴室用品
Button (Name) button1
Text Go
TextBox (Name) textBox1
Multiline True
四、撰寫程式碼
1. 建立委派函式。程式碼第 15 行,在全域變數區宣告 Dgate()委派,回
傳值型別為 void。
完整程式列表
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WindowsFormsApp1
12 {
13 public partial class Form1 : Form
14 {
15 delegate void Dgate();
16
17 public Form1()
18 {
19 InitializeComponent();
20 }
21
22 public void PreWords()
23 {
24 textBox1.AppendText("今日特價商品:");
25 }
26
27 public void Item()
28 {
29 if (radioButton1.Checked)
30 textBox1.AppendText("雙層鉛筆盒\r\n");
31 else
32 textBox1.AppendText("精美手工皂\r\n");
33 }
34
35 public void Sold()
36 {
37 Random rd = new Random();
38 int num;
39
40 num = rd.Next(1, 30);
41 textBox1.AppendText("只剩下 " + num + " 個\r\n");
42 }
43
44 private void Button1_Click(object sender, EventArgs e)
45 {
46 Dgate DelFunc1= PreWords;
47 Dgate DelFunc2=Item;
48 Dgate DelFunc3 = Sold;
49 Dgate AllDel;
50
51 AllDel = DelFunc1 + DelFunc2;
52 AllDel();
53
54 AllDel -= DelFunc2;
55 AllDel += DelFunc3;
56 AllDel();
57 }
58 }
59 }
15-2 索引子
索
引子(indexer)主要使用於當類別中的資料成員(例如:屬性)是集合
或是陣列形式時,利用索引子的特性,可以把類別所宣告的㇐個物件以陣
列索引的方式進行存取;當以陣列索引的方式存取物件時,即視為存取這些資料
成員。此外,結構、介面也能使用索引子。
範例 3:認識索引子
有㇐類別 MyClass,其有㇐個陣列形式的整數屬性 number,使用索引子的方式,
設定與讀取此屬性。
㇐、解說
在類別裡,索引子的宣告以 public 修飾字開始,並使用 this 識別字作為
存取資料成員的替身。回傳資料型別則與要透過索引子操作的資料成員的資
料型別相同。在 this 之後接著是如同陣列索引㇐般的[]中括弧;如下所示。
1 class 類別名稱
2 { 索引子
3
4 public 回傳資料型別 this[資料型別 索引變數]
5 {
6
7 }
8 }
1 class MyClass
2 {
3 int[] number=new int[10];
4
5 public int this[int index]
6 {
7 get { return number[index]; }
8 set { number[index] = value; }
9 }
10 }
1
2 MyClas myclass = new MyClass();
3
4 myclass[3] = 12;
5
number[3] = 12;
二、執行結果
下圖左為原始畫面。程式㇐開始執行時會先對 Myclass 的物件 myclass 初
始化,並產生 10 個亂數。按下「myclass」按鈕後,透過 myclass 的索引
子顯示此 10 個亂數,如下圖中所示。若按下「number」按鈕後,則直接讀
取 myclass 中的 number 屬性,如下圖右所示,並且發現透過索引子讀取
的資料與直接讀取 number 的資料,其實是相同的。
三、表單設計
請在表單上放置如下表所列之控制項。
控制項 屬性 設定值
Button (Name) button1
Text myclass
Button (Name) button2
Text number
TextBox (Name) textBox1
Multiline Treu
四、撰寫程式碼
1. 建立 MyClass 類別。程式碼第 46-62 行建立 MyClass 類別,第 48 行
宣告 public 修飾字修飾的整數陣列屬性 number。number 並不需要以
public 修飾字修飾;使用 public 修飾 number 只是為了方便能直接
讀取 number 的值,並與透過索引子讀取的值作比較而已。
分析與討論
索引子可以把類別宣告的㇐個物件,當成陣列索引般地使用,其實是為了要簡化
類別中的集合或是陣列成員的存取方式;但也容易造成程式碼不易了解與識別。
然而,使用傳統的方式㇐樣很方便:將此物件直接宣告為物件陣列、直接使用集
合或陣列成員原本的存取方式。
完整程式列表
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WindowsFormsApp1
12 {
13 public partial class Form1 : Form
14 {
15 MyClass myclass;
16 Random rd = new Random();
17
18 public Form1()
19 {
20 InitializeComponent();
21
22 myclass = new MyClass(10);
23
24 //透過索引子設定number屬性
25 for (int i = 0; i < 10; i++)
26 myclass[i] = rd.Next(100);
27 }
28
29 private void Button1_Click(object sender, EventArgs e)
30 {
31 //透過索引子讀取number屬性
32 for (int i = 0; i < 10; i++)
33 textBox1.AppendText(myclass[i].ToString() +
34 "\r\n");
35 }
36
37 private void Button2_Click(object sender, EventArgs e)
38 {
39 //直接讀取number屬性
40 for (int i = 0; i < 10; i++)
41 textBox1.AppendText(myclass.number[i].ToString() +
42 "\r\n");
43 }
44 }
45
46 class MyClass
47 {
48 public int[] number; //陣列形式的屬性
49
50 public MyClass(int num)
51 {
52 if (num <= 0)
53 num = 10;
54 number = new int[num];
55 }
56
57 public int this[int i] ///屬性number的索引子
58 {
59 get => number[i];
60 set => number[i] = value;
61 }
62 }
63 }
範例 4:多個索引子
有㇐類別 MyClass,其有 2 個陣列屬性:score 與 name;score 用於儲存分數,
而 name 用於儲存姓名。寫㇐程式使用索引子取得姓名與分數:首先輸入陣列的
索引值取得姓名,再由姓名當成索引,取出分數。
㇐、解說
類別裡對於㇐個集合或是陣列形式的資料成員,可以有多個索引子:2 個索
引子的回傳型別與索引值的資料型別都不㇐樣,即可視為視不同的索引子。
例如在某個類別裡有 2 個陣列屬性:
1
2 int []score = new int[5];
3 string []name = new string[5];
4
因為 2 個索引子的回傳值型別與索引值的資料型別都不同,所以被視為 2 個
不同的索引子。第㇐個索引子對屬性 score 存取,第 2 個索引子則對屬性
name 存取。
二、執行結果
下圖左為原始畫面。輸入陣列的索引編號後,按下「查詢」按鈕,可以透過
索引編號查詢人名,再由人名查詢分數;如下圖右所示。
三、表單設計
請在表單上放置如下表所列之控制項。
控制項 屬性 設定值
Label (Name) label1
Text 編號:
Button (Name) button1
Text 查詢
TextBox1 (Name) textBox1
Text 1
TextBox2 (Name) textBox2
Multiline True
四、撰寫程式碼
1. 建立類別 MyClass。程式碼第 46-94 行為別 MyClass,第 48-49 行宣
告 2 個陣列屬性,分別是整數陣列 score,用於儲存分數,以及字串陣
列 name,用於儲存姓名。第 51-57 行為類別 MyClass 的建構子,並帶
有㇐個參數 num,此參數用於設定 score 與 name 的陣列大小。第 53-
54 判斷若 num 小於等於 0,則將 num 設定為 5。第 55-56 行初始化並
配置陣列空間給 score 與 name。
完整程式列表
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Data;
5 using System.Drawing;
6 using System.Linq;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
10
11 namespace WindowsFormsApp1
12 {
13 public partial class Form1 : Form
14 {
15 public Form1()
16 {
17 InitializeComponent();
18 }
19
20 private void Button1_Click(object sender, EventArgs e)
21 {
22 int num = 5;
23 int index;
24 string str;
25 MyClass myclass = new MyClass(num);
26 int []score={80, 92, 67, 84, 60};
27 string[] name = { "王小明", "真美麗", "王老五",
28 "瑪莉", "約翰" };
29
30 for(int i=0;i<num; i++)
31 myclass.Add(i, name[i], score[i]);
32
33 index = Convert.ToInt32(textBox1.Text)-1;
34 if(index<0 || index>=num)
35 {
36 MessageBox.Show("無此編號");
37 return;
38 }
39
40 str=myclass[index];
41 textBox2.AppendText(str + "的分數=");
42 textBox2.AppendText(myclass[str].ToString() + "\r\n");
43 }
44 }
45
46 class MyClass
47 {
48 int[] score; //分數
49 string[] name; //姓名
50
51 public MyClass(int num)
52 {
53 if (num <= 0)
54 num = 5;
55 score = new int[num];
56 name = new string[num];
57 }
58
59 // 增加㇐筆資料:姓名、分數