委托
先来个例子:
三个需求:
1、将一个字符串数组中每个元素都转换成大写
2、将一个字符串数组中每个元素都转换成小写
3、将一个字符串数组中每个元素两边都加上 双引号
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
StrToUpper(names);
//StrSYH(names);
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
用三个方法分别去实现。
public static void StrToUpper(string[] name)
{
for (int i = 0; i < name.Length; i++)
{
name[i] = name[i].ToUpper();
}
}
public static void StrToLower(string[] name)
{
for (int i = 0; i < name.Length; i++)
{
name[i] = name[i].ToLower();
}
}
public static void StrSYH(string[] name)
{
for (int i = 0; i < name.Length; i++)
{
name[i] = "\"" + name[i] + "\"";
}
}
三个方法有大量相同重复代码,只有少部分是不同的。而且写完方法,三个功能要用三个函数,我们希望一个函数搞定。
首先解决用一个函数来实现这三个功能,将下面的三个函数名作为参数,来输入到这个函数中。用哪个函数名,就实现哪个函数的功能。
那么将函数名作为实参传入函数的这种操作,我们称为委托。
实质:将函数名作为参数传入函数中。将原函数要实现的功能,委托给一个新函数来实现。函数指针。指向函数。
那么函数名必须指定其类型,就是我们提到的委托类型:新的类型关键字:Delegate。
声明一个委托类型,委托所指向的函数,必须跟委托具有相同的签名(参数和返回值)。
如上,函数没有返回值,但是有一个string类型的输入。委托写的位置是:类外,命名空间里面。因此声明委托如下:
public delegate void DelStrTo(string[] name);
写一个方法,将委托作为参数传入,相当于传递一个方法过来。
public static void Test(string[] name, DelStrTo del)
{
del(name); //委托需要一个参数,写入name。
}
本来这个方法是要调用上面那三个函数来实现对应的功能。为了传递这三个方法,需要使用委托。委托里面装的就是三个函数中的其中一个。
接下来,我们调用Test方法。
委托的第一种使用方法:声明委托指向函数。
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
//StrToUpper(names);
//StrSYH(names);
//DelStrTo del = new DelStrTo(StrToUpper); // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
//DelStrTo del = new DelStrTo(StrToLower);
DelStrTo del = new DelStrTo(StrSYH);
del(names);
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
委托的第二种写法:直接把方法赋值给一个委托类型。
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
//StrToUpper(names);
//StrSYH(names);
//DelStrTo del = new DelStrTo(StrToUpper); // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
//DelStrTo del = new DelStrTo(StrToLower);
//DelStrTo del = new DelStrTo(StrSYH);
DelStrTo del = StrToUpper; // 方法直接赋值给委托类型。
DelStrTo del = StrToLower;
DelStrTo del = StrSYH;
del(names);
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
委托的第三种写法:既然可以赋值,那么就可以作为参数传递。
把一个方法作为参数,传递给另一个方法,那么参数应该有类型。方法作为参数的类型就是委托类型。
把函数赋值给一个委托类型,前提是这个委托的签名跟函数相同。
static void Main(string[] args)
{
//三个需求
//1、将一个字符串数组中每个元素都转换成大写
//2、将一个字符串数组中每个元素都转换成小写
//3、将一个字符串数组中每个元素两边都加上 双引号
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
//StrToUpper(names);
//StrSYH(names);
//DelStrTo del = new DelStrTo(StrToUpper); // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
//DelStrTo del = new DelStrTo(StrToLower);
//DelStrTo del = new DelStrTo(StrSYH);
//DelStrTo del = StrToUpper; // 直接把方法赋值给委托。
//DelStrTo del = StrToLower;
//DelStrTo del = StrSYH;
//del(names);
Test(names, StrToUpper);
Test(names, StrToLower);
Test(names, StrSYH);
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
}
上面说了半天不同写法。绕来绕去,还是不如最初的写法,直接调用方法多好。为此我们需要一点新的东西。
匿名函数
对于Test函数我们重新写一个:将上面三个函数共同的代码提取出来。
public static void Test(string[] names, DelStrTo del)
{
//del(name); //委托需要一个参数,写入name。
for (int i = 0; i < names.Length; i++)
{
names[i] = del(names[i]);
}
我们希望用委托将三个代码中不同的部分,委托掉。但是编译器此时会提醒。del无法从“string”转换为“string[]”。因此考虑字符串数组中,每个元素都是字符串,我们可以把委托输入的参数写为字符串,而不是原来的字符串数组。而且我们test方法已经遍历了,原来的方法只是处理每个元素,因此委托输入的参数应该为string类型,而不是string[]类型。并且有返回值string,而不是void。
public delegate string DelStrTo(string name);
此时洋气的写法有了。我们用匿名函数,也就是delegate里面以没有命名的方式实现了一个函数的方法。既有委托,又包含不同的实现方法。代码量大大减少。公共部分包含在test中,差异部分用匿名函数来简化。
public delegate string DelStrTo(string name);
class Program
{
static void Main(string[] args)
{
//三个需求
//1、将一个字符串数组中每个元素都转换成大写
//2、将一个字符串数组中每个元素都转换成小写
//3、将一个字符串数组中每个元素两边都加上 双引号
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
Test(names, delegate (string name) { return name.ToUpper(); });
Test(names, delegate (string name) { return name.ToLower(); });
Test(names, delegate (string name) { return "\"" + name + "\""; });
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
}
public static void Test(string[] name, DelStrTo del)
{
//del(name); //委托需要一个参数,写入name。
for (int i = 0; i < name.Length; i++)
{
name[i] = del(name[i]);
}
}
}
当方法只写一次,只用一次的时候,匿名函数是非常省力的方式。
嵌套写在里面可能有点复杂,我们看一个简单的例子。
我们考虑一个问题,Test函数还有用吗?
Test函数只是帮我们传递了一个委托,我们不用Test来传来传去了。Test的功能我们直接用匿名函数来实现。。
DelStrTo del = delegate (string[] names)
{
for (int i = 0; i < names.Length; i++)
{
names[i] = names[i].ToUpper();
}
};
del(names);
需要函数干的事情,交给委托,然后匿名就好了。委托和匿名函数的签名也必须一致。如果委托是string类型的,那么匿名函数,就需要有return。而此处我们只转换。
lambda表达式
还有一个洋气的写法:lambda表达式:匿名函数更为简写的形式。
static void Main(string[] args)
{
//三个需求
//1、将一个字符串数组中每个元素都转换成大写
//2、将一个字符串数组中每个元素都转换成小写
//3、将一个字符串数组中每个元素两边都加上 双引号
string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//DelStrTo del = delegate (string[] names)
//{
// for (int i = 0; i < names.Length; i++)
// {
// names[i] = names[i].ToUpper();
// }
//};
DelStrTo del = (string[] names) =>
{
for (int i = 0; i < names.Length; i++)
{
names[i] = names[i].ToUpper();
}
};
del(names);
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}
}
lambda表达式,中如果没有参数,可以将参数省去,但是括号不能省略。
符号=>表示goes to:参数传向方法体。
看几个lambda表达式的例子。
namespace _12_lambda表达式
{
public delegate void DelOne();
public delegate void DelTwo(string name);
public delegate string DelThree(string name);
class Program
{
static void Main(string[] args)
{
DelOne del = () => { };// delegate() { };
DelTwo del2 = (string name) => { };//delegate(string name) { };
DelThree del3 = (string name) => { return name; };//delegate(string name) { return name; };
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
list.RemoveAll(n => n > 4); //条件筛选器。
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
}
多播委托
委托不仅可以指向一个函数,还可以指向多个函数。
public delegate void DelTest();
class Program
{
static void Main(string[] args)
{
DelTest del = T1;
del += T2;
del += T3;
del+= T4;
del -= T3;
del -= T1;
del();
Console.ReadKey();
}
public static void T1()
{
Console.WriteLine("我是T1");
}
public static void T2()
{
Console.WriteLine("我是T2");
}
public static void T3()
{
Console.WriteLine("我是T3");
}
public static void T4()
{
Console.WriteLine("我是T4");
}
}
窗体传值
场景表述:
窗体1有一个按钮,一个label标签。点击按钮就可以打开窗体2。
窗体2有一个按钮,一个Txtbox文本框。点按钮就可以将窗体2中文本框的文本显示到窗体1中的标签label中。
private void btnfm1_Click(object sender, EventArgs e)
{
Form2 fm = new Form2();
fm.Show();
}
为了实现文本消息的传递,我们在窗体1中类内,将值传递给lable1,我们写一个方法:
public void ShowMsg(string str)
{
label1.Text = str;
}
这个str其实就是form2中的文本框的内容消息。
分析一下,目前的境地:窗体1也就是说Form1类中有方法把值赋值给label1,但是他没有值。窗体2也就是说Form2类中有要传递的值,但是没有传递的方法,方法在窗体1中Form1类中。
我们需要在Form2类中拿到Form1类中的方法就好了。谁可以把方法给传递过去。引出了委托。
声明一个委托,委托最终要指向我们Form1中的ShowMsg函数。被指向的函数签名:无返回值,有参数string。在Form2类中:
public delegate void DelShowMsg(string str);
怎么把方法传递过来?
方法在Form1中,委托在Form2中。怎么从Form1传递到Form2中。我们回看代码:在form1中,我们写了一个点击按钮初始化form2的方法,如果我们在初始化form2时可以把委托传进去就好了。构造函数可以做这事。new Form2(ShowMsg)。;这样Form2中既有方法又有值了。就能完成跨窗体传值这事。
为此我们需要重写Form2中的构造函数。在form2的构造函数中也需要一个委托类型的字段来接收传递过来的委托函数。Form2中,我们声明一个委托类型的字段。
public DelShowMsg _del;
传递过来的值赋值给字段。
namespace _02窗体传值
{
public delegate void DelShowMsg(string str);
public partial class Form2 : Form
{
public DelShowMsg _del;
public Form2(DelShowMsg del)
{
this._del = del;
InitializeComponent();
}
}
}
通过委托把函数传递过来了,我们怎么用?
窗体2拿到了方法,当点击发送按钮之后,就应该把值传递给Form1。form2中可以访问到form1中的这个方法,这个方法就在_del中。直接执行就好了。
Form2中的字段_del,就是Form1派过去的关于方法ShowMsg的代表。_del在Form2中行使Form1中ShowMsg的权力,执行其功能。
private void btnfm2_Click(object sender, EventArgs e)
{
_del(textBox1.Text);
}
form1中初始化Form2时,传递函数方法ShowMsg。
全码如下:
namespace _02窗体传值
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnfm1_Click(object sender, EventArgs e)
{
Form2 fm = new Form2(ShowMsg);
fm.Show();
}
public void ShowMsg(string str)
{
label1.Text = str;
}
}
}
namespace _02窗体传值
{
public delegate void DelShowMsg(string str);
public partial class Form2 : Form
{
public DelShowMsg _del;
public Form2(DelShowMsg del)
{
this._del = del;
InitializeComponent();
}
private void btnfm2_Click(object sender, EventArgs e)
{
_del(textBox1.Text);
}
}
}
泛型委托
场景:获取任意数组的最大值。
核心代码:if(max<names[i]){max = name[i]}也就是说max减去names[i]小于0。则赋值。比较函数委托。
namespace _11_泛型委托
{
public delegate int DelCompare<T>(T t1, T t2);
// public delegate int DelCompare(object o1, object o2);
class Program
{
static void Main(string[] args)
{
int[] nums = { 1, 2, 3, 4, 5 };
int max = GetMax<int>(nums, Compare1);
Console.WriteLine(max);
string[] names = { "abcdefg", "fdsfds", "fdsfdsfdsfdsfdsfdsfdsfsd" };
string max1 = GetMax<string>(names, (string s1, string s2) =>
{
return s1.Length - s2.Length;
});
Console.WriteLine(max1);
Console.ReadKey();
}
public static T GetMax<T>(T[] nums, DelCompare<T> del)
{
T max = nums[0];
for (int i = 0; i < nums.Length; i++)
{
//要传一个比较的方法
if (del(max, nums[i]) < 0)
{
max = nums[i];
}
}
return max;
}
public static int Compare1(int n1, int n2)
{
return n1 - n2;
}
}
}