只能打开程序的一个实例。
尝试思路:
1)将构造函数私有化。(按F12转到对应的类)
但私有化后,无法初始化,一个对象也创建不了。
2)提供一个静态方法,返回一个对象。
用静态方法来创建一个对象,尽管绕过上面私有构造,但仍然不是单例模式。
建立winform应用,再添加一个窗体form2,在form1上添加button1,双击button1,添加
Form2 f=new Form2();光标置Form2中按F12调出此类设置。
public partial class Form2 : Form
{
private Form2()//私有,无法调用
{
InitializeComponent();
}
public static Form2 Single()//静态,类名调用
{
Form2 f = new Form2();
return f;
}
}
此时:
private void button1_Click(object sender, EventArgs e)
{
Form2 f =Form2.Single();
f.Show();
}
仍然是多个实例。
3)根据2),创建一个全局静态窗体字段,并赋初值null。
在调用上面方法时判断是否为null,否则新创建。
写静态有两个目的,1全局,本类名都可访问。
2静态方法只能访问静态字段。
修改Form2:
public partial class Form2 : Form
{
private static Form2 f = null;
private Form2()
{
InitializeComponent();
}
public static Form2 Single()
{
if (f == null)
{
f = new Form2();
}
return f;
}
}
此时再次双击form1中的button1就是单例模式。
类似Html,html是用来显示数据的。
Xml是用来存储数据的,相当于一个轻型的数据库。
1、添加xml文件:
右击项目->添加->新建项..,(vs2022)在弹出窗体的右上角输入"xml"用于过滤,
窗体中间,选择"XML文件",下方输入名称,点添加即创建xml文件。
水浒传
10.5
拆迁户传奇
星汉灿烂
16.5
太可爱了
XML严格区别大小写。Html不是
XML也是成对出现。第一行是版本与编码信息。
元素:所有出现的内容都是元素。Element
节点:所有成对出现的标签都是节点。Node
元素包含节点。
xml必须要有根节点,且只能有一个根节点。
2、通过代码创建xml文档:
1)引用命名空间System.Xml;
2)创建XML文档对象;
3)创建第一行描述信息,并且添加到doc文档中。CreateXmlDeclaretion()
doc.AppendChild(dec)追加子集
4)创建根节点。
XmlElement books=doc.CreateElement("Books");
doc.AppendChild(books);
5)给根节点创建子节点。
XmlElemnet book1=doc.CreateElement("Book");
books.AppendChild(book1);//这里不是doc
6)给Book1添加子节点。
XmlElement name1=doc.CreateElement("Name");
name1.InnerText="水浒传";
book1.AppendChild(name1)
。。。。
7)doc.save()保存。
技巧:快速查看调试编译的文件。在解决方案中选中对应的项目,在解决方案资源管理
器小窗体的上面一行小图标中,选择”显示所有文件“,在项目就会出现bin和
obj两个隐藏目录。选择bin向下的Debug,里面就有我们编译的可执行文件,以
及本练习生成的相对目录中的Books.xml,可以右击选择打开,就在vs2022中
直接打开了。
private static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
doc.AppendChild(dec);
XmlElement books = doc.CreateElement("Books");
doc.AppendChild(books);
XmlElement book1 = doc.CreateElement("Book");
books.AppendChild(book1);
XmlElement name1 = doc.CreateElement("Name");
name1.InnerText = "水浒传";
book1.AppendChild(name1);
XmlElement price1 = doc.CreateElement("Price");
price1.InnerText = "10.5";
book1.AppendChild(price1);
XmlElement ms1 = doc.CreateElement("Dec");
ms1.InnerText = "108拆迁户";
book1.AppendChild(ms1);
//第一种写法,容易出错:
book1 = doc.CreateElement("Book");
books.AppendChild(book1);
XmlElement name2 = doc.CreateElement("Name");
name2.InnerText = "星汉灿烂";
book1.AppendChild(name2);
XmlElement price2 = doc.CreateElement("Price");
price2.InnerText = "16.5";
book1.AppendChild(price2);
XmlElement ms2 = doc.CreateElement("Dec");
ms2.InnerText = "好可爱";
book1.AppendChild(ms2);
第二种写法更快,更不容易出错。去掉类型声明,修改内容即可
book1 = doc.CreateElement("Book");//此句不能省,必须创建新的空的。否则下句直接加入上面内容。
books.AppendChild(book1);
name1 = doc.CreateElement("Name");
name1.InnerText = "翻身心法";
book1.AppendChild(name1);
price1 = doc.CreateElement("Price");
price1.InnerText = "100.5";
book1.AppendChild(price1);
ms1 = doc.CreateElement("Dec");
ms1.InnerText = "心机";
book1.AppendChild(ms1);
doc.Save("Books.xml");
}
每次添加新的结点如book1时,必须是新的。如果和前面一样,结点将不会添加。
结点发生更改,哪些这个结点是空的,它也会添加
有些XML是"非正常"的:
刘洋
bj0000001
把上面的用代码写一下。注意设置属性SetAttribute(Key,Value)
另外一个结点的追加是上一级结点。
快速查看xml的方法:
private static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
doc.AppendChild(dec);
//根
XmlElement order = doc.CreateElement("Order");
doc.AppendChild(order);
XmlElement name = doc.CreateElement("CustomerName");
name.InnerText = "刘洋";
order.AppendChild(name);
XmlElement num = doc.CreateElement("CustomerNumber");
num.InnerText = "bj0000001";
order.AppendChild(num);
XmlElement items = doc.CreateElement("Items");
order.AppendChild(items);
XmlElement orderitem = doc.CreateElement("OrderItem");
orderitem.SetAttribute("Name", "码表");
orderitem.SetAttribute("Count", "2");
items.AppendChild(orderitem);
orderitem = doc.CreateElement("OrderItem");
orderitem.SetAttribute("Name", "雨衣");
orderitem.SetAttribute("Count", "12");
items.AppendChild(orderitem);
orderitem = doc.CreateElement("OrderItem");
orderitem.SetAttribute("Name", "手套");
orderitem.SetAttribute("Count", "3");
items.AppendChild(orderitem);
doc.Save("Order.xml");
}
InnerText与InnerXml的区别
InnerText:里面全是文字时,用InnerText。(否则有转义符)
InnerXml: 里面有标签时,用InnerXml.
3、对已存在的Xml进行编辑
删除结点
1)先进行加载读取。
2)选中结点
3)RemoveAll()删除结点
private static void Main(string[] args)
{
XmlDocument doc = new XmlDocument();
doc.Load("Order.xml");
XmlNode xn = doc.SelectSingleNode("/Order/Items/OrderItem");//同名第一个节点
xn = xn.NextSibling;//下一节点
xn.RemoveAll(); //删除当前结点下面的子节点及属性。注意本节点仍在,只是为空了。
xn = doc.SelectSingleNode("/Order/Items");
xn.RemoveChild(xn.LastChild);//删除最后一个子节点.
//上面第二个与第三个子结点都是空的,因为同级结点不能相同,所以只能保留一个。
xn.RemoveAll();//删除所有子节点
doc.Save("Order.xml");
}
1、为什么要使用委托?
举例:假如有三个需求:
1、将一个字符串数组中的每个元素都转换成小写;
2、将一个字符串数组中的每个元素都转换成大写;
3、将一个字符串数组中的每个元素两边都加上双引号
案例1:
private static void Main(string[] args)
{
string[] s = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//ProcessStrUpper(s);
//ProcessStrLower(s);
ProcessStrFlag(s);
foreach (string s2 in s)
{
Console.WriteLine(s2);
}
Console.ReadKey();
}
public static void ProcessStrUpper(string[] strs)
{
for (int i = 0; i < strs.Length; i++)
{
strs[i] = strs[i].ToUpper();
}
}
public static void ProcessStrLower(string[] strs)
{
for (int i = 0; i < strs.Length; i++)
{
strs[i] = strs[i].ToLower();
}
}
public static void ProcessStrFlag(string[] strs)
{
for (int i = 0; i < strs.Length; i++)
{
strs[i] = "\"" + strs[i] + "\"";
}
}
上面实现三个方法,代码类似,仅里面的大写,小写,加引号不同。也就是说这三个
方法基本相同,唯一不同的就是大小、小写、加引号---这三件事不同,也即里面
要求做的事不同,做事---对应就是C#中的方法。
如果把做的事(对应C#就是方法)当作参数,就可以节约代码量,同时比较整洁。
以前当作参数都是数据类型等,把方法作为一参数还是第一次接触。
小结:
将一个方法作为参数,传递给另一个方法。这参数(方法)的类型就是委托类型。
2、委托的概念
声明一个委托类型。
委托所指向的函数必须跟委托具有相同的签名(参数与返回值相同)
/命名空间下:声明一个委托函数
案例2:
public delegate void DelSayHi(string name);
internal class Program
{
private static void Main(string[] args)
{
DelSayHi d = new DelSayHi(SayChinese);
d("张三");
DelSayHi d1 = SayEnglish;//简写,实际同上。
d1("李四");
//上面与Test无关。但我们最终目的就是使用Test,
Test("王五", SayChinese);//将方法作参数(委托类型),传入,取得结果
Test("赵六", SayEnglish);
Console.ReadKey();
}
public static void Test(string name, DelSayHi del)
{
del(name);
}
public static void SayChinese(string name)
{
Console.WriteLine("你吃了吗?" + name);
}
public static void SayEnglish(string name)
{
Console.WriteLine("How do you do?" + name);
}
}
上例说明:
1)说汉语与说英语方法类似,用委托。
2)将方法作为参数,其类型就是委托类型。方法是对于类中,为了全局把委托
写入了命名空间下。
3)第一次d用类似对象进行构造,相当于把方法的地址告诉委托对象d,然后
传入参数“张三”后,委托就自动调用地址对应的方法执行。
相当于委托是第三方,即不是直接的方法,也不是直接的参数,它就是一
个二传手,中介机构。但却有机地把两者结合在一起了。
4)第二d2直接把方法给委托对象d1,也即,方法可以直接赋值给委托对象。
在反编译中可以看出,方法2实际与方法1是一样的。
5)第三次Test,于是直接用方法赋值给委托类型。前提是两者的签名(参数
与返回值是一样的。看一下:
public delegate void DelSayHi(string name);
public static void SayChinese(string name)
public static void SayEnglish(string name)
6)第四次Test,同5)
小结:
1)可将方法直接赋值给委托,前提是方法与委托的签名一致。
2)尽管使用了委托这个方法,但还没有真正展现出委托的好处和目的。
3、匿名函数
回到案例1后,根据委托改写一下:
public delegate string DelProcStr(string str);
internal class Program
{
private static void Main(string[] args)
{
string[] s = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
ProcessStr(s, StrToFlag);
Console.WriteLine(string.Join("-", s));
Console.ReadKey();
}
public static void ProcessStr(string[] strs, DelProcStr del)
{
for (int i = 0; i < strs.Length; i++)
{
strs[i] = del(strs[i]);
}
}
public static string StrToUpper(string str)//大写
{
return str.ToUpper();
}
public static string StrToLower(string str)//小写
{
return str.ToLower();
}
public static string StrToFlag(string str)//加引号
{
return "\"" + str + "\"";
}
}
说明:
1)把里面的大写,小写,加引号的方法单独列出来,统一成委托,放在命名下定义。
2)单独再写一个方法,里面灵活地用方法DelProcStr del作为参数,传入到ProcessStr中
由里面的strs[i] = del(strs[i])处理。
因此保管传入的委托方法,相当于一个统一的接口,不用写死,一万种方法都可以
用这种传参方式进行处理。
3)坏处:三个方法没少,还多了一个单独的ProcessStr。代码量没有发生变化。
匿名函数:
没有名字的函数。它只有类型与参数。直接在里面写入代码。
上例把三个方法注释掉,在主函数中重写一下:
public delegate string DelProcStr(string str);
internal class Program
{
private static void Main(string[] args)
{
string[] s = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
ProcessStr(s, delegate (string str)
{
return str.ToLower();
}
);
Console.WriteLine(string.Join("-", s));
Console.ReadKey();
}
public static void ProcessStr(string[] strs, DelProcStr del)
{
for (int i = 0; i < strs.Length; i++)
{
strs[i] = del(strs[i]);
}
}
}
说明:主函数中
ProcessStr(s, delegate (string str)
{
return str.ToLower();
}
);
传入的方法参数,没有有函数名,只有类型delegate和参数string str,后面直接象写方法一样
把代码写在它的{}内即可。
所以把没有名字的函数或方法称之为匿名函数。
匿名函数的适用范围:
这种函数只调用一次,那么可以使用这种不需函数名,直接在调用中定入代码的方式。
再看一个案例,注意使用的方式。理解委托类型,方法作为参数。
namespace Test
{
public delegate void DelSay(string name);
internal class Program
{
private static void Main(string[] args)
{
SayHi("张三", SayChinese);//方法一
DelSay ds = new DelSay(SayEnglish);//方法二
ds("李四");
SayHi("赵六", delegate (string s)//方法三
{
Console.WriteLine("您好," + s);
});
//方法四
DelSay ds1 = delegate (string s)
{
Console.WriteLine("Hello," + s);
};
ds1("王五");
//方法五 Lamda表达式
DelSay ds2 = (string s) => { Console.WriteLine("思密达," + s); };
ds2("赵拓");
Console.ReadKey();
}
public static void SayHi(string name, DelSay del)//打招呼
{
del(name);
}
public static void SayChinese(string name)//说中文
{
Console.WriteLine("你好," + name);
}
public static void SayEnglish(string name)//说英文
{
Console.WriteLine("Hello," + name);
}
}
}
说明:
1)此例中有两个类似的方法:说中文/说英文,一个统一的打招呼,以及主函数的调用。
2)方法一,中规中矩,通过委托,将方法作为参数在打招呼中使用。
此方法,说中/英文,与打招呼,三个方法都不能省略。代码量稍多。
3)方法二,与创建对象类似,要用到原始的说中/英文方法,但打招呼方法可以省略。
此方法的实际执行体在SayEnglish中,ds只是说明类型实体引用。后一句的
调用ds("李四")只是传递一个参数,实体还是在SayEnglish中。
ds("李四")理解成只是传递参数,就不会陷于以前理解方法的模式。但实际上
委托就是调用方法的地址(或指针),同时这个地址也可直接赋值给ds,所以
相当于ds也是指向这个执行体(方法)的地址(函数指针),也即在调用方法体
中的代码,加上"李四"这个参数,就完成了执行条件。
因此,根据函数方法也可以赋值委托,也可以写成:
DelSay ds2 = SayChinese;
ds("诸葛亮");//因此,相当于SayChinese("诸葛亮");
4)方法三,带入了方法体,所以说中/英文两个方法可以省略。
SayHi(name,del)是一个正常的方法。只是参数del传入的是方法的地址,这个
地址就是代码方法体的地方。
如果不带参数则应该是delegate(),也即虽然参数没有了但括号仍然存在。
如果有返回值,则在代码方法体中写出return,类型则声明委托时的返回值类型。
5)方法四,由于有了代码方法体,方法说中/英文这两个方法可以省略。同时,
ds1("王五")又传入了前面声明委托时的参数name,所以打招呼方法也可省略。
此方法是匿名方法。若无参则为ds1=delegate(){...}
若有返回值,则应在{...}写明return,返回的类型由前面声明委托返回类型写明.
6)方法五,可以省略说中/英文两个方法,打招呼方法也省略。
这是Lamda表达式,前面的(string s)表示参数,无参时括号也不能省略。
=>表示goes to(滚向),即把参数送入到后面的代码方法体中。
{....}是代码方法体。
ds2("赵拓") ds2就是上句代码方法体的内存地址.相当于把"赵拓"送入方法体中.
例子:有返回值。简写情况。
public delegate int GetGnum(int n);
private static void Main(string[] args)
{
GetGnum d = delegate (int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
{
sum += i;
}
return sum;
};
Console.WriteLine(d(100).ToString());
Console.ReadKey();
}
说明:
将delegate (int n)写成lamda更为简便:(int n)=>
4、练习:使用委托求任意数组的最大值.(类型未说,为object)
分析对比一个int与string数组比较方法的异同
public static int GetMax(int[] n)
{
int max = n[0];
for (int i = 0; i < n.Length; i++)
{
if (max < n[i])
{
max = n[i];
}
}
return max;
}
public static string GetMax(string[] n)
{
string max = n[0];
for (int i = 0; i < n.Length; i++)
{
if (max.Length < n[i].Length)
{
max = n[i];
}
}
return max;
}
上面两个方法可以重载。并且写法为了迎合委托方向,进行了刻意。因为最终的目的就是让其中
的一句变得不一样(省略号部分),即条件比较部分---一个比较的方法.
public static object GetMax(object[] n)
{
object max = n[0];
for (int i = 0; i < n.Length; i++)
{
if (......)
{
max = n[i];
}
}
return max;
}
这个条件比较方法返回是bool,也可以转换一下,差值与0。
public delegate int DelCompare(object o1, object o2);
internal class Program
{
private static void Main(string[] args)
{
object[] n1 = { 1, 2, 3, 4, 5, 6 };
object max1 = GetMax(n1, Compare1);
Console.WriteLine(max1.ToString());
object[] s = { "ab", "c", "adk", "lsidie", "wel", "23" };
object max2 = GetMax(s, Compare2);
Console.WriteLine(max2.ToString());
Console.ReadKey();
}
public static object GetMax(object[] n, DelCompare del)
{
object max = n[0];
for (int i = 0; i < n.Length; i++)
{
if (del(max, n[i]) < 0)
{
max = n[i];
}
}
return max;
}
public static int Compare1(object o1, object o2)//整形
{
int n1 = (int)o1;
int n2 = (int)o2;
return n1 - n2;
}
public static int Compare2(object o1, object o2)//字串
{
string s1 = (string)o1;
string s2 = (string)o2;
return s1.Length - s2.Length;
}
}
说明:compare1与compare2必须完全符合委托类型,两个参数object,返回值int.
为了省略两个比较方法,也可以写成匿名函数,或者lamda表达式:
private static void Main(string[] args)
{
object[] n1 = { 1, 2, 3, 4, 5, 6 };
object max1 = GetMax(n1, delegate (object o1, object o2)
{
int n2 = (int)o1;//不能再用n1,已经被前面参数使用
int n3 = (int)o2;
return n2 - n3;
});
Console.WriteLine(max1.ToString());
object[] s = { "ab", "c", "adk", "lsidie", "wel", "23" };
object max2 = GetMax(s, (object o1, object o2) =>
{
string s1 = (string)o1;
string s2 = (string)o2;
return s1.Length - s2.Length;
});
Console.WriteLine(max2.ToString());
Console.ReadKey();
}
注意:匿名函数与lamda表达式的基础是委托,因此前面声明的委托类型不能省略。
提示:委托学一两遍不懂很正常,不断练不断写,不断看不断想,
理解了函数地址指针,基本上就知道了。
委托学会了,事件也就简单了。如果委托没学会,事件肯定搞不明白。
两句话:
1声明委托,就是声明了一种类型,一种对自定义“方法”的类型。
2委托的对象就是函数内存地址(的引用?)
小结:
这个写法绕来绕去,一是代码量好像增加了;二是用了object有拆装箱。唯一
的好处就:结构清晰,功能明白。
5、泛型委托
上例中,发生了拆装箱,为了解决这个矛盾,使用泛型,让参数与方法用泛型。
也就是泛型委托。
//public delegate int DelCompare(object o1, object o2);
public delegate int DelCompare(T o1, T o2);
internal class Program
{
private static void Main(string[] args)
{
int[] n = { 2, 3, 4, 1, 3, 45, 33, 2 };
int max = GetMax(n, Compare1);
Console.WriteLine(max.ToString());
string[] s = { "ad", "ksldi", "k", "oeisl", "isdl", "lk" };
string max1 = GetMax(s, (string s1, string s2) =>
{
return s1.Length - s2.Length;
});
Console.WriteLine(max1);
Console.ReadKey();
}
public static T GetMax(T[] n, DelCompare del)
{
T max = n[0];//改为T
for (int i = 0; i < n.Length; i++)
{
if (del(max, n[i]) < 0)
{
max = n[i];
}
}
return max;
}
public static int Compare1(int n1, int n2)
{
return n1 - n2;
}
}
说明:
1)从委托声明开始就必须开始用T来代替说明类型;
2)被调用的方法后也须要紧跟
3)匿名函数与lamda表达式因为省略了方法名,所以后面紧跟的
6、Lamda表达式
lamda表达式本质就是委托。
注意看一下lamda的三种情况的写法
namespace ConsoleApp1
{
public delegate void DelOne();//无参无返回
public delegate void DelTwo(string name);//有参无返回
public delegate string DelThree(string name);//有参有返回
internal class Program
{
private static void Main(string[] args)
{
//无参无返回
DelOne del1 = delegate () { };
DelOne del2 = () => { };
//有参无返回
DelTwo del3 = delegate (string name) { };
DelTwo del4 = (string name) => { };
//有参有返回
DelThree del5 = delegate (string name) { return name; };
DelThree del6 = (string name) => { return name; };
List list = new List() { 1, 2, 3, 4, 5, 6 };
list.RemoveAll(i => i < 4);
foreach (int n in list)
{
Console.WriteLine(n);
}
Console.ReadKey();
}
}
}
说明:list.RemoveAll(i => i < 4);也是lamda表达方法,谓词。其中i也可骒任意
字母。比如也可写成:list.RemoveAll(k => k < 4);
7、使用委托来进行窗体传值
form1与form2之间的信息传输。
过程:form1中有button1,label1; form2中有button1,textbox1.
form1显示后点按钮显示form2,在form2中输入abc,让其传输到form1中
的label1中显示。
两者不做修改前是无法进行,form1无法调用form2中的变量或控件。
第一种传统的:
1)把form1通过在form2的构造函数,传递到form2中,这样就可以调用form1了.
2)在form1的初始化InitializeComponent(); 按F12,进入里面的代码,把lable1
改为public。因为要在类外form2中调用form1中的控件label1.
form1修改初始化中label1为public:
public System.Windows.Forms.Label label1;
然后窗体from1代码:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form2 fr2 = new Form2(this);
fr2.Show();
}
}
窗体form2代码:
public partial class Form2 : Form
{
private Form1 _f1;
public Form2(Form1 f1)
{
InitializeComponent();
_f1 = f1;
}
private void button1_Click(object sender, EventArgs e)
{
_f1.label1.Text = textBox1.Text;
}
}
这样,form1通过form2的构造函数把自身传到form2中的字段_f1,就可以在form2中直接
引用form2了。
第二:使用委托。
form1要显示内容,写一个显示方法Show(string);有方法但无内容(内容在form2)
form2中要传送内容,但没有方法(方法在form1)。
于是想办法,用委托(方法作为参数),因此form1把方法要传递到form2中去。
同样也是通过在form1中创建form2时,用构造函数时把方法传递过去.
此方法:不用修改form1中的label1为public.
form1窗体:
//要传方法,委托应写与方法形式
//必须在类外,这样form2中才能生效
public delegate void DelShow(string s);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Form2 fr2 = new Form2(Show);
fr2.Show();
}
private void Show(string str)//private
{
label1.Text = str;
}
}
form2窗体:
public partial class Form2 : Form
{
private DelShow _d;
public Form2(DelShow d)
{
_d = d;
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
_d(textBox1.Text);
}
}
引申思考:如果把form1中的label1信息传到form2中TextBox1中呢?
同方法一,把form2中textbox1设置public。在form1中的fr2设置为公共变量.
这样创建后可以引用到.
private Form2 fr2;//form1类下
fr2.textBox1.Text = label1.Text;//这样就可以直接访问了。
小结:
委托的好处
1.相当于用方法作为另一方法参数(类似于C的函数指针)
2.在两个不能直接调用的方法中作为桥梁.
比如:在多线程中的跨线程的方法调用就得用委托;
上例中两个窗体间传送信息。
3.当不知道方法具体实现什么时使用委托,如:事件中使用委托。
8、多播委托(MulticastDelegate)
namespace Test1
{
public delegate void DelMulti();
internal class Program
{
private static void Main(string[] args)
{
DelMulti d = T1;
d();//T1
Console.WriteLine();
d += T2; d += T3; d += T4; d += T5;
d();//T1,T2,T3,T4,T5
Console.WriteLine();
d -= T2; d -= T4;
d();//T1,T3,T5
Console.WriteLine();
d += T1;
d();//T1,T3,T5,T1
Console.WriteLine();
d += T1; //多加会进入列表
d();//T1,T3,T5,T1,T1
Console.WriteLine();
d -= T1; d -= T1; d -= T1;
d();//T3,T5
Console.WriteLine();
d -= T1;//多减不会影响列表
d();//T3,T5
Console.WriteLine();
d -= T3;
//d -= T5; //最后一个T5,再减T5会异常
d -= T3;//最后一个T5,再减T3却不会异常。
d();
Console.WriteLine();
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");
}
public static void T5()
{
Console.WriteLine("我是T5");
}
}
}
说明:
多播委托,其实例会将多个委托加入列表中。列表重复不会出错,列表不会空时
减去不会异常。
当最后一个时,若减去相同委托实例,则会异常。不同的实例不会异常和影响。
下面的来自
https://www.zhangshengrong.com/p/wradZARBXB/
9、委托的统述
1)什么是委托?
委托是寻址方法的.NET版本,使用委托可以将方法作为参数进行传递。委托是一种特
殊类型的对象,其特殊之处在于委托中包含的只是一个活多个方法的地址,而不是数据。
委托虽然看起来像是一种类型,但其实定义一个委托,是定义了一个新的类。下面这行
代码:
//定义委托,它定义了可以代表的方法的类型,但其本身却是一个类
public delegate int methodDelegate(string str);
定义了一个委托,使用ILDasm.exe查看其生成的IL代码如图所示:
.NET将委托定义为一个类,派生自基类System.MulticastDelegate,并继承了基类的
三个方法(稍后讨论这三个)。
2)委托与函数指针的区别
1、安全性:
C/C++的函数指针只是提取了函数的地址,并作为一个参数传递它,没有类型安全性,
可以把任何函数传递给需要函数指针的地方;而.NET中的委托是类型安全的。
2、与实例的关联性:
在面向对象编程中,几乎没有方法是孤立存在的,而是在调用方法前通常需要与类实
例相关联。委托可以获取到类实例中的信息,从而实现与实例的关联。
3、本质上函数指针是一个指针变量,分配在栈中;委托类型声明的是一个类,实例化
为一个对象,分配在堆中。
4、委托可以指向不同类中具有相同参数和签名的函数,函数指针则不可以。
namespace Test1
{
//定义委托,它定义了可以代表的方法的类型,但其本身却是一个类
public delegate void methodDelegate(string str);
internal class Program
{
private static void Main(string[] args)
{
Student student = new Student();
Teacher teacher = new Teacher("王老师");
methodDelegate del = new methodDelegate(student.GetStudentName);
del += teacher.GetTeacherName; //可以指向不同类中的方法!
//del += teacher.GetClassName; //指向签名不符的方法时提示错误!
del.Invoke("张三");
Console.ReadLine();
}
}
internal class Student
{
private String name = "";
public Student(String _name)
{
this.name = _name;
}
public Student()
{ }
public void GetStudentName(String _name)
{
if (this.name != "")
Console.WriteLine("Student's name is {0}", this.name);
else
Console.WriteLine("Student's name is {0}", _name);
}
}
internal class Teacher
{
private String name;
public Teacher(String _name)
{
this.name = _name;
}
public void GetTeacherName(String _name)
{
if (this.name != "")
Console.WriteLine("Teacher's name is {0}", this.name);
else
Console.WriteLine("Teacher's name is {0}", _name);
}
public string GetClassName()
{
return "English";
}
}
}
结果:
Student's name is 张三
Teacher's name is 王老师
3)C#中实现委托有哪些方式及各自主要适用范围。
(1)、常规实现
internal class Program
{
private delegate String GetAString();
private static void Main(String[] args)
{
int temp = 40;
GetAString stringMethod = new GetAString(temp.ToString);
//函数名。temp.ToString()则错误。
Console.WriteLine("String is {0}", stringMethod());
//这里stringMethod()等价于调用temp.ToString();
Console.ReadLine();
}
}
说明:
这段代码中,实例化了类型为GetAString的一个委托,并对它进行初始化,使它
引用整型变量temp的ToString()方法。
在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的
方法。
上例中stringMethod()等价于使用temp.ToString(),同时也与调用委托类的
Invoke()方法完全相同,实际上,如下图IL代码中红色部分所示,C#编译器会用
stringMethod.Invoke()代替stringMethod()。
为了简便输入,C#支持只传送地址的名称给委托的实例(委托推断),如下两行
代码在编译器看来是一样的。
GetAString stringMethod1 = new GetAString(temp.ToString);
GetAString stringMethod1 = temp.ToString;
实际上委托的实例可以引用任何类型的任何对象上的实例方法或静态方法,只要
方法的签名匹配于委托的签名即可。所以结构体的方法一样可以传递给委托。
注意:不要被tem.ToString迷惑了。可以看作:
temp.ToString()=temp.ToString + ()
前面是“方法名”,后面()是传参的。
(2)、多播委托
多播委托具有一个带有链接的委托列表,称为调用列表,在对委托实例进行调用
的时候,将按列表中的委托顺序进行同步调用。
如果委托有返回值,则将列表中最后一个方法的返回值用作整个委托调用的返回
值。因此,使用多播委托通常具有void返回类型。
可以使用+=来使委托指向多个方法的地址,但必须是在委托实例化之后才可以使
用+=来添加新的方法地址(添加重复的方法地址编译器不会报错,但是也不会重复执
行),若想移除其中的方法地址可以使用-=来实现(需要至少保留一个,即对于最后
一个方法地址的移除不起作用,会引发异常)。以下代码中下面两行无论单独保留哪
行,最终的执行结果都是相同的。
GetAString stringMethod = new GetAString(temp.ToString);
stringMethod += temp.ToString;
stringMethod -= temp.ToString;
(3)、委托数组
namespace Test1
{
internal delegate double Operations(double x);
internal class Program
{
private static void Main()
{
Operations[] operations ={
MathOperations.MultiplyByTwo,
MathOperations.Square
};
for (int i = 0; i < operations.Length; i++)
{
Console.WriteLine("Using operations[{0}]:", i);
DisplayNumber(operations[i], 2.0);
DisplayNumber(operations[i], 7.94);
Console.ReadLine();
}
}
private static void DisplayNumber(Operations action, double value)
{
double result = action(value);
Console.WriteLine("Input Value is {0}, result of operation is {1}", value, result);
}
}
internal struct MathOperations
{
public static double MultiplyByTwo(double value)
{
return value * 2;
}
public static double Square(double value)
{
return value * value;
}
}
}
上述代码中实例化了一个委托数组operations(与处理类的实例相同),该数组的
元素初始化为MathOperations类的不同操作,遍历这个数组,可以将每个操作应用到2个
不同的值中。这种用法的好处是,可以在循环中调用不同的方法。