1.匿名方法
没有具体名称,只有委托关键字、方法参数、方法体,所以称之为匿名方法
匿名方法允许将代码块(方法体部分)通过委托变量作为参数传递,以代替独立定义的方法
总结:
方法体要以delegate开头,其后是参数列表,后面紧跟花括号编写方法体,最后以分号结尾
匿名方法是将方法定义与委托变量两个步骤合在一起,省略了方法的单独定义
如果一个方法有一个委托类型的参数,可以直接将匿名方法作为实参。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //声明委托 2 public delegate int MyDelegate(int a, int b); 3 4 static void Main(string[] args) 5 {//匿名方法 6 MyDelegate objDel = delegate (int a, int b) 7 { 8 return a + b; 9 }; 10 Console.WriteLine(objDel(10, 20)); 11 Console.ReadLine(); 12 }
2.lambda表达式
C#3.0引入lambda表达式,lambda表达式是匿名方法更简介使用的一种方式
(参数列表) => {方法体} =>读作goes to
lambda表达式参数列表中的参数类型可以是“明确”类型或者是“推断类型”(可以省略参数类型)
如果是推断类型,参数数据类型将由编译器根据上下文自动推断出来
一般情况下还是写参数类型的好,方便自己和他人理解
如果参数只有一个可以省略();如果方法体只有一行代码可以省略{}
3.lambda表达式总结
两种方式:(input args)=>表达式 (input args)=>{语句1;语句2;......}
举例1:只有一个参数的时候,括号可选 Func<int,bool> del1=(x)=>{return x>0;};
举例2:可简化为 Func<int,bool> del2=x=>x>0;
举例3:两个或更多输入参数由逗号分隔 Func<int,int,bool> del3=(x,y)=>x==y;
举例4:没有参数时,直接输入一个空的括号 Action del4=()=>{...}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //声明委托 2 public delegate int MyDelegate(int a, int b); 3 static void Main(string[] args) 4 { 5 //一条语句未省略{} 6 MyDelegate objDel0 = (int a, int b) => { return a + b; }; 7 //一条语句省略{} 8 MyDelegate objDel1 = (int a, int b) => a + b; 9 Console.WriteLine(objDel0(10, 20)); 10 Console.WriteLine(objDel1(10, 20)); 11 Console.ReadLine(); 12 }
4.lambda与匿名方法比较
Lambda表达式本身就是匿名方法
Lambda表达式的参数可以允许不指明参数类型,而匿名方法的参数必须明确指明参数类型
Lambda表达式方法体允许由单一表达式或多条语句组成,而匿名方法不允许单一表达式形式
5.委托引入
委托本质:是一个类,继承自System.MulticastDelegate,里面内置了几个方法(Invoke()、BeginInvoke()、EndInvoke())
委托的声明:public delegate void MyDelete(int num); 细看委托的声明,如果把delegate去掉就是一个方法的声明。
委托的实例化:实例化委托之前必须写好委托实例传入的方法,这个方法和委托声明必须有相同的返回值和参数。
委托的调用:Invoke()也可以直接调用。
委托的三大作用:【1】逻辑解耦,减少重复代码【2】异步多线程【3】多播委托,基于观察者模式
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//【1】委托声明 public delegate void NoReturnWithPara(int para); //【2】委托传入的方法 private void TheMethod(int num) { Console.WriteLine($"我就是那个数{num}"); } public void Show() { //【3】实例化委托 NoReturnWithPara noReturnWithPara = new NoReturnWithPara(TheMethod); //【4】调用委托实例 noReturnWithPara.Invoke(22); noReturnWithPara(11); } //【5】委托:可以把方法当成参数进行传递
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 委托解耦的实例说明 * 背景:一群学生,姓名、年龄、住址各异,要求筛选出特定条件的学生个数。 * 例如筛选出名字为2个字的学生、年龄大于20的学生、家住昆山的学生、和满足以上所有的学生 * 最笨的方法(暂不考虑linq),【1】先新建一个list、【2】foreach挨个遍历、【3】判断条件满足、【4】加入list、【5】输出list个数 * 细分析以上5个步骤:只有步骤3判断条件满足这里不一样,别的四个步骤都一样 * 四个一样的步骤叫共有逻辑、3判断条件满足叫独有逻辑,解耦解的是这两者之间的耦合关系,在3判断条件满足这里使用委托 */ public class DelegateTest { //准备数据 public List<Student> students = new List<Student>() { new Student() { Name="张三", Age=36, Address="昆山高新区" }, new Student() { Name="李四平", Age=32, Address="昆山开发区" }, new Student() { Name="王麻子", Age=18, Address="上海青浦区" }, new Student() { Name="赵六", Age=36, Address="天津黑芝麻胡同" } }; //【1】定义委托 public delegate bool JudgeDele(Student student); //【2】准备判断条件 public bool JudgeNameEqual2(Student student) { return student.Name.Length == 2; } public bool JudgeAgeUp20(Student student) { return student.Age >= 20; } public bool JudgeAddressInKun(Student student) { return student.Address.Contains("昆山"); } public bool JudgeAll(Student student) { return student.Name.Length == 2 && student.Age >= 20 && student.Address.Contains("昆山"); } //判断逻辑传递进来+实现共用逻辑,委托解耦,减少重复代码 public int GetStudents(List<Student> students, JudgeDele dele) { List<Student> listStu = new List<Student>(); foreach (Student student in students) { if (dele.Invoke(student))//【4】利用委托代替判断方法 { listStu.Add(student); } } return listStu.Count(); } } //【3】实例化委托 DelegateTest delegateTest = new DelegateTest(); int NameEqual2 = delegateTest.GetStudents(delegateTest.students, delegateTest.JudgeNameEqual2); int AgeUp20 = delegateTest.GetStudents(delegateTest.students, delegateTest.JudgeAgeUp20); int AddressInKun = delegateTest.GetStudents(delegateTest.students, delegateTest.JudgeAddressInKun); int JudgeAll = delegateTest.GetStudents(delegateTest.students, delegateTest.JudgeAll); Console.WriteLine($"NameEqual2:{NameEqual2}"); Console.WriteLine($"AgeUp20:{AgeUp20}"); Console.WriteLine($"AddressInKun:{AddressInKun}"); Console.WriteLine($"JudgeAll:{JudgeAll}");
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int LingNameEqual2 = (from Student student in delegateTest.students where student.Name.Length == 2 select student).Count(); int LingAgeUp20 = (from Student student in delegateTest.students where student.Age > 20 select student).Count(); int LingAddressInKun = (from Student student in delegateTest.students where student.Address.Contains("昆山") select student).Count(); int LingJudgeAll = (from Student student in delegateTest.students where student.Name.Length == 2 && student.Age > 20 && student.Address.Contains("昆山") select student).Count(); Console.WriteLine($"LingNameEqual2:{LingNameEqual2}"); Console.WriteLine($"LingAgeUp20:{LingAgeUp20}"); Console.WriteLine($"LingAddressInKun:{LingAddressInKun}"); Console.WriteLine($"LingJudgeAll:{LingJudgeAll}");
5.多播委托
多播委托【+=】:为委托实例按顺序增加方法,形成方法链,Invoke时,按顺序依次执行。
多播委托【-=】:为委托实例按顺序移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常。移除时不是同一个实例的方法移除不了。多播委托不能异步(BeginInvoke和EndInvoke)多播委托带返回值Invoke以最后的结果为准。
多播委托的价值:一个变量保存多个方法,可以增减:invoke的时候可以按顺序执行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//【1】定位委托 public delegate int WithReturnNoPara(); //【2】多播委托的三个方法 private int GetInt1() { return 1; } private int GetInt2() { return 2; } private int GetInt3() { return 3; } public void Show() { //【3】实例化委托 WithReturnNoPara withReturnNoPara = new WithReturnNoPara(GetInt1); //【4】添加多播委托 withReturnNoPara += new WithReturnNoPara(GetInt2); withReturnNoPara += new WithReturnNoPara(GetInt3); foreach (WithReturnNoPara dele in withReturnNoPara.GetInvocationList()) {//遍历多播委托 Console.WriteLine($"遍历结果 {dele.Invoke()}"); } Console.WriteLine($"Invoke()方法返回值:{withReturnNoPara.Invoke()}");//多播委托带返回值Invoke以最后的结果为准 //【5】减去多播委托的一个方法 withReturnNoPara -= new WithReturnNoPara(GetInt3); foreach (WithReturnNoPara dele in withReturnNoPara.GetInvocationList()) { Console.WriteLine($"遍历结果 -=之后{dele.Invoke()}"); } Console.WriteLine($"Invoke()方法返回值:-=之后{withReturnNoPara.Invoke()}"); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /* 多播委托应用举例,引入事件 2 * 场景:一直神奇的猫,在它叫了一声之后会其他引发一系列的行为 3 */ 4 //【1】定义委托 5 public delegate void MiaoDelegate(); 6 public class Cat 7 { 8 //不适用多播委托和事件 9 //直接调用别的实例别的方法,如果有一系列增删改,会让此方法不稳定 10 public void Miao() 11 { 12 Console.WriteLine("{0} Miao", this.GetType().Name); 13 new Mouse().Run(); 14 new Baby().Cry(); 15 new Mother().Wispher(); 16 new Brother().Turn(); 17 new Father().Roar(); 18 new Neighbor().Awake(); 19 new Stealer().Hide(); 20 new Dog().Wang(); 21 } 22 23 //分析场景:猫叫了一声,引发一系列后续动作 24 //可以用多播委托将一系列动作封装出去【甩锅的概念】 25 //【2】定义委托实例 26 public MiaoDelegate MiaoDelegateHandler = null; 27 public void MiaoNew() 28 { 29 Console.WriteLine($"{this.GetType().Name} 叫了一声"); 30 if (MiaoDelegateHandler != null) 31 { 32 MiaoDelegateHandler.Invoke(); 33 } 34 } 35 36 //事件的引入 37 public event MiaoDelegate MiaoDelegateHandlerEvent = null; 38 public void MiaoNewEvent() 39 { 40 Console.WriteLine($"{this.GetType().Name} 叫了一声"); 41 if (MiaoDelegateHandlerEvent != null) 42 { 43 MiaoDelegateHandlerEvent.Invoke(); 44 } 45 } 46 } 47 48 49 //多播委托应用 50 Cat cat = new Cat(); 51 Console.WriteLine("~~~~~~~~~~~~~多播委托~~~~~~~~~~~~~"); 52 cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run); 53 cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry); 54 cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher); 55 cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn); 56 cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar); 57 cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake); 58 cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide); 59 cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang); 60 cat.MiaoNew();
5.事件event
事件是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值,子类也不能调用。并且事件只能由声明者才能调用。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 多播委托应用举例,引入事件 * 场景:一直神奇的猫,在它叫了一声之后会其他引发一系列的行为 */ //【1】定义委托 public delegate void MiaoDelegate(); public class Cat { //分析场景:猫叫了一声,引发一系列后续动作 //可以用多播委托将一系列动作封装出去【甩锅的概念】 //【2】定义委托实例 public MiaoDelegate MiaoDelegateHandler = null; public void MiaoNew() { Console.WriteLine($"{this.GetType().Name} 叫了一声"); if (MiaoDelegateHandler != null) { MiaoDelegateHandler.Invoke(); } } //事件的引入 public event MiaoDelegate MiaoDelegateHandlerEvent = null; public void MiaoNewEvent() { Console.WriteLine($"{this.GetType().Name} 叫了一声"); if (MiaoDelegateHandlerEvent != null) { MiaoDelegateHandlerEvent.Invoke(); } } } Cat cat = new Cat(); Console.WriteLine("~~~~~~~~~~~~~多播委托~~~~~~~~~~~~~"); cat.MiaoDelegateHandler += new MiaoDelegate(new Mouse().Run); cat.MiaoDelegateHandler += new MiaoDelegate(new Baby().Cry); cat.MiaoDelegateHandler += new MiaoDelegate(new Mother().Wispher); cat.MiaoDelegateHandler += new MiaoDelegate(new Brother().Turn); cat.MiaoDelegateHandler += new MiaoDelegate(new Father().Roar); cat.MiaoDelegateHandler += new MiaoDelegate(new Neighbor().Awake); cat.MiaoDelegateHandler += new MiaoDelegate(new Stealer().Hide); cat.MiaoDelegateHandler += new MiaoDelegate(new Dog().Wang); cat.MiaoNew(); Console.WriteLine("~~~~~~~~~~~~~事件~~~~~~~~~~~~~"); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mouse().Run); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Baby().Cry); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Mother().Wispher); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Brother().Turn); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Father().Roar); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Neighbor().Awake); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Stealer().Hide); cat.MiaoDelegateHandlerEvent += new MiaoDelegate(new Dog().Wang); cat.MiaoNewEvent();
委托与事件的区别和联系?委托是一个类型,事件是委托的一个实例。事件是带event关键字的委托的实例,event可以限制变量被外部调用/直接赋值
5.事情与程序架构
事件:可以把一堆可变的动作/行为封装出去,交给第三方来指定。和预定义一样,程序设计的时候,可以把程序分成两部分:一部分是固定的,直接写死;还有不固定的,通过一个事件去开放接口,外部可以随意扩展动作。
框架:完成固定或通用部分,把可变部分留出扩展点,支持自定义。
5.自定义泛型委托
当一类相同方法类似,但方法的参数类型不一致的时候可以考虑使用泛型委托
因为是泛型,所以在定义委托时用T表示参数类型,在定义委托变量时再指定方法类型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //1.定义泛型委托 2 public delegate T MyDele<T>(T obj1, T obj2); 3 class Program 4 { 5 //2.定义委托方法 6 static int Add(int a, int b) => a + b; 7 static double Sub(double a, double b) => a - b; 8 static void Main(string[] args) 9 { 10 //3.定义委托变量 11 MyDele<int> objIntDele = Add; 12 MyDele<double> objDouDele = Sub; 13 //4.使用委托变量 14 Console.WriteLine(objIntDele(10, 20)); 15 Console.WriteLine(objDouDele(2.2, 5.7)); 16 Console.ReadLine();//使用新特性 using static System.Console; 17 } 18 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //定义泛型委托 2 public delegate T MyGenericDeleage<T>(T obj1, T obj2); 3 class Program 4 { 5 static int Add(int a, int b) 6 { 7 return a + b; 8 } 9 static double Sub(double a, double b) 10 { 11 return a - b; 12 } 13 14 //泛型委托:匿名方法、Lambda表达式 15 static void Main(string[] args) 16 { 17 //【1】使用委托 18 MyGenericDeleage<int> objDelegate1 = Add; 19 MyGenericDeleage<double > objDelegate2 = Sub; 20 21 Console.WriteLine(objDelegate1(10,20)); 22 Console.WriteLine(objDelegate2(10, 20)); 23 Console.WriteLine("-----------------"); 24 25 //【2】使用匿名方法 26 MyGenericDeleage<int> objDelegate3 = delegate(int a, int b) { return a + b; }; 27 MyGenericDeleage<double > objDelegate4 = delegate(double a, double b) { return a - b; }; 28 29 Console.WriteLine(objDelegate3(10, 20)); 30 Console.WriteLine(objDelegate4(10, 20)); 31 Console.WriteLine("-----------------"); 32 33 //【3】使用Lambda表达式 34 MyGenericDeleage<int> objDelegate5 = (a, b) => a + b; 35 MyGenericDeleage<double> objDelegate6 = (a, b) => a - b; 36 37 Console.WriteLine(objDelegate5(10, 20)); 38 Console.WriteLine(objDelegate6(10, 20)); 39 Console.ReadLine(); 40 } 41 }
6.Func<T>委托 :最后一个参数为返回值类型
Func<>委托是微软自定义好的泛型委托
Func委托声明的最后一个泛型类型是委托所接受方法的返回值类型
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 static double Add(int a, int b) 2 { 3 return a + b; 4 } 5 static void Main(string[] args) 6 { 7 Func<int, int, double> func0 = Add; 8 double result = func0(10, 20); 9 //使用Lambda表达式 10 Func<int, int, double> func1 = (a, b) => a + b; 11 Console.WriteLine(func0(10, 20)); 12 Console.WriteLine(func1(10, 20)); 13 Console.ReadLine(); 14 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Func<int,int,string> func = (x,y) => (x*y).ToString(); Consle.WriteLine(func(5,20));
7.编写一个方法,从数组中指定位置抽取3个数,求和、求积 int[] nums={10,9,8,7,6,5,4,3,2};
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 static void Main(string[] args) 2 { 3 int[] nums = { 10, 9, 8, 7, 6, 5, 4, 3, 2 }; 4 Console.WriteLine("------不使用委托-------"); 5 Console.WriteLine(GetAdd(nums, 0, 3)); 6 Console.WriteLine(GetMulti(nums, 0, 3)); 7 Console.WriteLine("------使用Func委托-------"); 8 Console.WriteLine(CommmonCalcu(Add, nums, 0, 3)); 9 Console.WriteLine(CommmonCalcu(Multi, nums, 0, 3)); 10 Console.WriteLine("------使用Func委托+Lambda表达式-------"); 11 Console.WriteLine(CommmonCalcu((int a, int b) => a + b, nums, 0, 3)); 12 Console.WriteLine(CommmonCalcu((int a, int b) => a * b, nums, 0, 3)); 13 Console.ReadLine(); 14 } 15 16 static int CommmonCalcu(Func<int,int,int> openation,int[] nums,int from,int to) 17 { 18 int result = nums[from]; //无论加乘,都需要使用第一个元素 19 for (int i = from+1; i <= to; i++) 20 { 21 result = openation(result, nums[i]); 22 } 23 return result; 24 } 25 26 static int Add(int a, int b) 27 { 28 return a + b; 29 } 30 31 static int Multi(int a, int b) 32 { 33 return a * b; 34 } 35 36 static int GetAdd(int[] nums, int from, int to) 37 { 38 int result = 0; 39 for (int i = from; i <= to; i++) 40 { 41 result += nums[i]; 42 } 43 return result; 44 } 45 46 static int GetMulti(int[] nums, int from, int to) 47 { 48 int result = 1; 49 for (int i = from; i <= to; i++) 50 { 51 result *= nums[i]; 52 } 53 return result; 54 }
Func委托引用一个有返回值的方法,也就是将方法作为另一个方法的“参数”
8.Action委托
Func委托必须要求接受的方法有一个返回值
Action委托接受一个没有返回值的方法,返回值为void
应用:在跨线程访问可视化控件的时候经常使用Action委托
9.Predicate委托
Predicate<T>委托定义如下:
public delegate bool Predicate<T>(T obj);
Predicate<T>委托变量引用一个“判断条件函数”,条件满足返回true。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 static void Main(string[] args) 2 { 3 List<Student> ListAll = new List<Student>() 4 { 5 new Student(){stuID=10001,stuName="小杨"}, 6 new Student(){stuID=10002,stuName="小朱"}, 7 new Student(){stuID=10003,stuName="小王"}, 8 new Student(){stuID=10004,stuName="小李"}, 9 new Student(){stuID=10005,stuName="小刘"}, 10 new Student(){stuID=10006,stuName="小张"}, 11 new Student(){stuID=10007,stuName="小梅"} 12 }; 13 //List<T>集合中定义了一个FindAll方法:public T FindAll(Predicate<T> match) 14 List<Student> list = ListAll.FindAll(s => s.stuID > 10003); 15 foreach (var item in list) 16 { 17 Console.WriteLine(item.stuName + ":" + item.stuID); 18 } 19 Console.ReadLine(); 20 }
10.FindAll方法与Linq语句与Linq方法查询比较
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 static void Main(string[] args) 2 { 3 List<Student> ListAll = new List<Student>() 4 { 5 new Student(){stuID=10001,stuName="小杨"}, 6 new Student(){stuID=10002,stuName="小朱"}, 7 new Student(){stuID=10003,stuName="小王"}, 8 new Student(){stuID=10004,stuName="小李"}, 9 new Student(){stuID=10005,stuName="小刘"}, 10 new Student(){stuID=10006,stuName="小张"}, 11 new Student(){stuID=10007,stuName="小梅"} 12 }; 13 //List<T>集合中定义了一个FindAll方法:public T FindAll(Predicate<T> match) 14 List<Student> list0 = ListAll.FindAll(s => s.stuID > 10003); 15 Console.WriteLine("----------使用Predicate委托----------"); 16 foreach (var item in list0) 17 { 18 Console.WriteLine(item.stuName + ":" + item.stuID); 19 } 20 Console.WriteLine("----------使用Linq语句查询----------"); 21 var list1 = from stu in ListAll 22 where stu.stuID > 10003 23 select stu; 24 foreach (var item in list1) 25 { 26 Console.WriteLine(item.stuName + ":" + item.stuID); 27 } 28 Console.WriteLine("----------使用Linq方法查询----------"); 29 var list2 = ListAll.Where(s => s.stuID > 10003) 30 .Select(s => s); 31 foreach (var item in list2) 32 { 33 Console.WriteLine(item.stuName + ":" + item.stuID); 34 } 35 Console.ReadLine(); 36 }
11.泛型再结
委托三大意义:【1】委托解耦【2】异步多线程【3】
泛型好处:类型安全、方便编码、无需拆装箱操作
常见泛型:泛型类和泛型方法
后续深入:泛型委托(自定义泛型委托、Func、Action、Predicate)
提取不变的,封装变化的
12.泛型类的规范 public class 类名<T>{类的成员...}
T:仅仅表示一个占位符,只要符合C#的命名规范即可使用,但一般都是用T
T:表示一个通用的数据类型,在使用的时候用实际类型代替
T:泛型类可以在定义中包含多个任意类型的参数,参数之间用多个逗号分隔开
例如 class MyGenericClass<T1,T2,T3>{...}
各种类型参数可以用作成员变量的类型、属性或方法等成员的返回类型已经方法的参数类型等。
13.泛型应用于出入栈
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 编写一个入栈和出栈操作的通用类 3 /// </summary> 4 /// <typeparam name="T">可以是任意类型</typeparam> 5 public class MyStack<T> 6 { 7 private T[] stack; 8 private int size;//栈数据容量 9 private int stackPoint;//当前位置指针 10 11 public MyStack(int size) 12 { 13 this.size = size; 14 stack = new T[size]; 15 stackPoint = -1; 16 } 17 18 /// <summary> 19 /// 入栈方法 20 /// </summary> 21 /// <param name="item"></param> 22 public void Push(T item) 23 { 24 if (stackPoint >= size) 25 { 26 Console.WriteLine("栈已满"); 27 } 28 else 29 { 30 stackPoint++; 31 stack[stackPoint] = item; 32 } 33 } 34 /// <summary> 35 /// 出栈方法 36 /// </summary> 37 /// <returns></returns> 38 public T Pop() 39 { 40 T data = this.stack[stackPoint]; 41 stackPoint--; 42 return data; 43 } 44 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //栈:先进后出 2 static void Main(string[] args) 3 { 4 MyStack<int> objStack = new MyStack<int>(5); 5 objStack.Push(1); 6 objStack.Push(2); 7 objStack.Push(3); 8 objStack.Push(4); 9 objStack.Push(5); 10 Console.WriteLine(objStack.Pop()); 11 Console.WriteLine(objStack.Pop()); 12 Console.WriteLine(objStack.Pop()); 13 Console.WriteLine(objStack.Pop()); 14 Console.WriteLine(objStack.Pop()); 15 Console.ReadLine(); 16 }
14.default关键字在泛型类中的使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class MyGenericClass1<T1, T2> 2 { 3 private T1 obj1; 4 5 private MyGenericClass1() 6 { 7 //obj1 = null; //不能这么用 8 //obj1 = new T1();//不能随便假设某种类型,这种类型也许没有构造方法,也许是私有的。 9 10 //解决方法:default关键字 11 obj1 = default(T1);//如果T1是引用类型,就赋值null,如果是值类型就给默认值,对于数值类型就是0,结构类型的话要依据具体的成员类型确定为0或者null 12 } 13 }
15.添加带约束的泛型类
约束泛型类:泛型类的参数类型受约束,例如泛型类有三个泛型参数,第一个要求必须是值类型、第二个要求必须是引用类型,诸如此类的约束。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class MyGenericCalss2<T1, T2, T3> 2 where T1 : struct //说明:类型必须是值类型 3 where T2 : class //说明:类型必须是引用类型 4 where T3 : new() //说明:类型必须有一个无参数的构造方法,且必须放到最后 5 //其他类型:基类类型、接口类型 6 { 7 public List<T2> ProductList { get; set; } //产品列表 8 public T3 Publisher { get; set; } //发行者 9 public MyGenericCalss2() 10 { 11 ProductList = new List<T2>(); 12 Publisher = new T3(); 13 } 14 /// <summary> 15 /// 购买第几个产品 16 /// </summary> 17 /// <param name="num">产品索引</param> 18 /// <returns>购买的产品</returns> 19 public T2 Buy(T1 num) 20 { 21 // return ProductList[num];//直接写是错误的。因为数组通过下标访问的时候必须是int类型,而int和T1又是冲突的 22 dynamic a = num; //动态类型,在编译时解析 23 return ProductList[a]; 24 } 25 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 课程(产品类) 3 /// </summary> 4 class Course 5 { 6 /// <summary> 7 /// 课程名称 8 /// </summary> 9 public string CourseName { get; set; } 10 /// <summary> 11 /// 学习周期 12 /// </summary> 13 public int Period { get; set; } 14 } 15 /// <summary> 16 /// 课程老师(发行人) 17 /// </summary> 18 class Teacher 19 { 20 public string Name { get; set; }//姓名 21 public int Count { get; set; }//授课数量 22 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 static void Main(string[] args) 2 { 3 //【1】实例化泛型类型对象 4 MyGenericCalss2<int, Course, Teacher> myClass2 = new MyGenericCalss2<int, Course, Teacher>(); 5 //【2】给对象属性赋值 6 myClass2.Publisher = new Teacher() { Name = "常老师", Count = 20 }; //课程的发行人 7 myClass2.ProductList = new List<Course>() //课程(产品)列表 8 { 9 new Course(){CourseName =".NET-CS+BS高级工程师VIP班",Period=6}, 10 new Course(){CourseName =".NET-CS高级工程师VIP班",Period=3}, 11 new Course(){CourseName =".NET-BS高级工程师VIP班",Period=3}, 12 }; 13 //【3】调用对象方法 14 Course myCourse = myClass2.Buy(0); 15 //数据处理 16 string info = string.Format("我购买的课程是:{0} 学期:{1}个月 课程主讲:{2}", 17 myCourse.CourseName, 18 myCourse.Period, 19 myClass2.Publisher.Name); 20 Console.WriteLine(info); 21 Console.ReadLine(); 22 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 自己写的泛型约束类,模拟一个商品列表,购买以及商品发行人的实例 3 /// </summary> 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 MyGenericClass<int, Course, Teacher> objGeneric = new MyGenericClass<int, Course, Teacher>(); 9 objGeneric.Publisher = new Teacher() { TeaName = "杨老师" }; 10 objGeneric.Products = new List<Course>() 11 { 12 new Course(){CourseName="C#教程",Period=12}, 13 new Course(){CourseName="VB教程",Period=10}, 14 new Course(){CourseName="SQLServer教程",Period=6} 15 }; 16 Course objCourseBuy = objGeneric.Buy(1); 17 string info = string.Format("购买的课程是{0},课程授课时长是{1},课程主讲老师是{2}", 18 objCourseBuy.CourseName, objCourseBuy.Period, objGeneric.Publisher.TeaName); 19 Console.WriteLine(info); 20 Console.ReadLine(); 21 } 22 } 23 24 /// <summary> 25 /// 商品交互委托泛型类 26 /// </summary> 27 /// <typeparam name="T1">商品索引</typeparam> 28 /// <typeparam name="T2">商品列表</typeparam> 29 /// <typeparam name="T3">商品发行人</typeparam> 30 public class MyGenericClass<T1, T2, T3> 31 where T1 : struct //T1必须是值类型(值类型包括整型、浮点型、decimal、bool、enum) 32 where T2 : class //T2必须是引用类型(引用类型包括数组、class、interface、delegate、object、string) 33 where T3 : new() //T3必须有一个空的构造函数 34 { 35 public T3 Publisher; //发行人 36 public List<T2> Products = new List<T2>(); //商品 37 public T2 Buy(T1 index) 38 { 39 dynamic t1 = index; 40 return Products[t1]; 41 } 42 } 43 44 /// <summary> 45 /// 课程(商品)类 46 /// </summary> 47 public class Course 48 { 49 /// <summary> 50 /// 课程名称 51 /// </summary> 52 public string CourseName { get; set; } 53 /// <summary> 54 /// 课程周期 55 /// </summary> 56 public int Period { get; set; } 57 } 58 59 /// <summary> 60 /// 老师(发行人)类 61 /// </summary> 62 public class Teacher 63 { 64 /// <summary> 65 /// 老师姓名 66 /// </summary> 67 public string TeaName { get; set; } 68 }
16.泛型方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine(Add1("大家好", "我是帅气的杨三少")); 6 Console.WriteLine("10+20={0}", Add1(10, 20)); 7 Console.WriteLine("10.2+20.3={0}", Add1(10.2, 20.3)); 8 Console.WriteLine("10+20.6={0}", Add1(10, 20.6)); 9 Console.ReadLine(); 10 } 11 12 static T Add1<T>(T a, T b) 13 { 14 dynamic a1 = a; 15 dynamic b1 = b; 16 return a1 + b1; 17 } 18 19 static T Add2<T>(T a, T b) where T : struct 20 { 21 dynamic a1 = a; 22 dynamic b1 = b; 23 return a1 + b1; 24 } 25 }
17.使用委托从窗体向子窗体发送消息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//1.在主窗体定义委托 public delegate void CountDele(string str); public partial class FrmMain : Form { public FrmMain() { InitializeComponent(); FrmOther objOther = new FrmOther(); //4.将委托变量与方法关联 objOther.objCountDele = ShowCount; objOther.Show(); } //2.在主窗体定义委托方法 private void ShowCount(string str) { lbl_Count.Text = str; } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //3.定义委托变量 2 public CountDele objCountDele; 3 private int count = 0; 4 public FrmOther() 5 { 6 InitializeComponent(); 7 } 8 9 private void btn_1_Click(object sender, EventArgs e) 10 { 11 count++; 12 //5.委托代替方法执行 13 objCountDele(count.ToString()); 14 }
18.使用委托主窗体向多个从窗体传递消息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //1.定义委托 2 public delegate void DeleCount(string str); 3 public partial class FrmMain : Form 4 { 5 //3.定义委托变量 6 private DeleCount objDeleCount; 7 private int count = 0; 8 public FrmMain() 9 { 10 InitializeComponent(); 11 12 FrmOther01 frm01 = new FrmOther01(); 13 FrmOther02 frm02 = new FrmOther02(); 14 FrmOther03 frm03 = new FrmOther03(); 15 //4.委托变量关联委托方法 16 objDeleCount += frm01.ShowCount; 17 objDeleCount += frm02.ShowCount; 18 objDeleCount += frm03.ShowCount; 19 frm01.Show(); 20 frm02.Show(); 21 frm03.Show(); 22 } 23 24 private void btn_Add_Click(object sender, EventArgs e) 25 { 26 //5.调用委托变量传递消息 27 count++; 28 objDeleCount(count.ToString()); 29 } 30 31 private void btn_Clear_Click(object sender, EventArgs e) 32 { 33 //5.调用委托变量传递消息 34 count = 0; 35 objDeleCount(count.ToString()); 36 } 37 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //2.定义委托方法 2 public void ShowCount(string str) 3 { 4 lbl_Count.Text = str; 5 }
19.事件event
事件:事件是对象在外界“刺激”下发生事情,而对外提供的一种消息机制。
事件就是委托类型。
事件的两个参与者:
发送者(Sender):即对象本身,当本身状态发生变化时触发事件,并通知事件的接收者。
接收者(Receiver):用来处理事件的(事件的关注者),在事件发送者触发一个事件后,会自动执行的内容。
事件一般的使用规则:委托声明在主窗体类内。委托对象也叫委托实例或委托变量或事件event定义在事件的发送者所在类中,委托调用的方法定义在事件的接收者所在类中,事件关联在主窗体内,事件代替方法实现调用在发送者所在类内。
20.主窗体统一向多个从窗体发送消息(类似广播),与委托类似
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //1.声明委托 2 public delegate void DeleSendMsg(string msg); 3 public partial class FrmMain : Form 4 { 5 //3.定义事件 6 public event DeleSendMsg EventSendMsg; 7 public FrmMain() 8 { 9 InitializeComponent(); 10 FrmClient01 frm1 = new FrmClient01(); 11 FrmClient02 frm2 = new FrmClient02(); 12 //4.将主窗体的事件与客户端窗体的接收方法关联起来 13 EventSendMsg += frm1.Receive; 14 EventSendMsg += frm2.Receive; 15 frm1.Show(); 16 frm2.Show(); 17 } 18 19 private void btn_SendMsg_Click(object sender, EventArgs e) 20 { 21 //5.使用事件发送消息 22 EventSendMsg(tb_Msg.Text.Trim()); 23 } 24 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //2.定义客户端窗体的接收方法 2 public void Receive(string msg) 3 { 4 tb_Msg.Text = msg; 5 }
21.从窗体向主窗体发送消息,要求在主窗体中显示是来自谁的消息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //1.定义委托 2 public delegate void DeleSendMsg(string msg); 3 public partial class FrmMain : Form 4 { 5 public FrmMain() 6 { 7 InitializeComponent(); 8 FrmClient1 frm1 = new FrmClient1(); 9 FrmClient2 frm2 = new FrmClient2(); 10 //5.事件关联方法 11 frm1.EventSendMsg += Receice; 12 frm2.EventSendMsg += Receice; 13 frm1.Show(); 14 frm2.Show(); 15 } 16 17 //2.定义主窗体接收消息的方法 18 private void Receice(string msg) 19 { 20 tb_Msg.Text += msg + " "; 21 } 22 23 private void btn_ClearMsg_Click(object sender, EventArgs e) 24 { 25 tb_Msg.Text = ""; 26 } 27 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //3.定义事件 2 public event DeleSendMsg EventSendMsg; 3 public FrmClient1() 4 { 5 InitializeComponent(); 6 } 7 8 private void btn_SendMsg_Click(object sender, EventArgs e) 9 { 10 //4.事件发送消息 11 EventSendMsg("客户端1的消息:" + tb_Msg.Text.Trim()); 12 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //3.定义事件 2 public event DeleSendMsg EventSendMsg; 3 public FrmClient2() 4 { 5 InitializeComponent(); 6 } 7 8 private void btn_SendMsg_Click(object sender, EventArgs e) 9 { 10 //4.事件发送消息 11 EventSendMsg("客户端2的消息:" + tb_Msg.Text.Trim()); 12 }
22.委托与事件的比较
事件在内部使用可以用“=”,如果是外部使用事件对象必须放在“+=”或“-=”的左边。
相同点:事件对象本质就是一个私有的委托对象,以及公有的两个方法,add和remove。
不同点:+=方式,实际上是调用add方法对委托对象进行添加。
事件对象私有化后,无法直接从外部赋值(内部赋值除外)。例如:事件=null;会出现编译错误,而委托允许这样做。
好处:避免用户直接将对象清除。比如微软给开发者所定义的各种事件,只允许开发者通过+=方式使用,而不允许开发者把定义好的事件清除掉。Click=null;这是不允许的。只能通过-=方式断开事件,这样能起到保护作用,而委托“太开放”
23.异步编程
异步编程:核心是基于委托实现的。
委托类型的BeginInvoke(<输入和输出变量>,AsyncCallBack callBack,object ayncState)方法是异步调用的核心
第一个参数10,表示委托对应的方法实参
第二个参数CallBack,回调函数,表示异步调用结束后,自动调用的方法
第三个参数AsyncState,用于向回调函数提供相应的参数信息
返回值:IAsyncResult异步操作状态接口,封装了异步执行的参数
委托类型的EndInvoke(IAsyncResult)方法
参数是BeginInvoke方法的返回值
返回值:异步执行的结果,返回值类型和委托类型、委托方法的返回值类型一致
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //异步编程的核心是基于委托 2 public partial class FrmMain : Form 3 { 4 //异步执行【1】.声明委托 5 public delegate int DeleExecute(int a); 6 public FrmMain() 7 { 8 InitializeComponent(); 9 } 10 11 //同步执行 12 private void btnTest1_Click(object sender, EventArgs e) 13 { 14 lblInfo1.Text = ExecuteTask1(30).ToString(); 15 lblInfo2.Text = ExecuteTask2(40).ToString(); 16 } 17 18 //异步执行【3】.异步调用 19 private void btnTest2_Click(object sender, EventArgs e) 20 { 21 //定义委托变量,引用相应方法 22 DeleExecute objDeleExecute = new DeleExecute(ExecuteTask1); 23 24 //通过委托异步调用方法ExecuteTask1 25 //委托类型的BeginInvoke(<输入和输出变量>,AsyncCallBack callBack,object ayncState)方法是异步调用的核心 26 //第一个参数10,表示委托对应的方法实参 27 //第二个参数CallBack,回调函数,表示异步调用结束后,自动调用的方法 28 //第三个参数AsyncState,用于向回调函数提供相应的参数信息 29 //返回值:IAsyncResult异步操作状态接口,封装了异步执行的参数 30 31 //【1】.异步调用任务 32 IAsyncResult result = objDeleExecute.BeginInvoke(50, null, null); 33 lblInfo1.Text = "异步执行开始计算..."; 34 //【2】.可以并行执行其他任务 35 lblInfo2.Text = ExecuteTask2(60).ToString(); 36 //【3】.获取异步执行的结果 37 //委托类型的EndInvoke(IAsyncResult)方法 38 //参数是BeginInvoke方法的返回值 39 //返回值:异步执行的结果,返回值类型和委托类型、委托方法的返回值类型一致 40 int r = objDeleExecute.EndInvoke(result); 41 lblInfo1.Text = r.ToString(); 42 } 43 44 //异步编程【2】.定义委托方法 45 private int ExecuteTask1(int a) 46 { 47 System.Threading.Thread.Sleep(5000); 48 return a * a; 49 } 50 51 private int ExecuteTask2(int a) 52 { 53 return a * a; 54 } 55 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //同时执行10个任务,第i个任务演示i秒,计算返回i的平方 2 namespace _016异步编程执行多个任务 3 { 4 //【1】.声明委托 5 public delegate int DeleExecuteTasks(int a,int ms); 6 public partial class FrmMain : Form 7 { 8 //【3】.创建委托变量,因为异步函数和回调函数都要用,所以定义为成员变量 9 private DeleExecuteTasks objDeleExecuteTasks; 10 public FrmMain() 11 { 12 InitializeComponent(); 13 //【3】.初始化委托变量 14 objDeleExecuteTasks = new DeleExecuteTasks(ExecuteTask); 15 } 16 17 //【2】.定义委托方法 18 private int ExecuteTask(int a, int ms) 19 { 20 System.Threading.Thread.Sleep(ms); 21 return a * a; 22 } 23 //【4】.异步同时执行多个任务 24 private void btn_AsyncTasks_Click(object sender, EventArgs e) 25 { 26 //使用循环发布多个任务 27 for (int i = 1; i < 11; i++) 28 { 29 //开始异步执行 30 //前两个参数与异步方法内的参数一致 31 //AsyncCallBack:回调函数 32 //object @object:最后一个参数是给回调函数的字段AsyncState(IAsyncResult.AsyncState)赋值,如果参数很多可以定义成类或结构 33 objDeleExecuteTasks.BeginInvoke(10 * i, 1000 * i, MyAsyncCallBack, i); 34 } 35 } 36 37 //【5】.回调函数:作用是当任务完成后的反馈机制,回调函数可以有自己的自定义参数 38 private void MyAsyncCallBack(IAsyncResult result) 39 { 40 int r = objDeleExecuteTasks.EndInvoke(result); 41 //IAsyncResult.AsyncState用来封装回调函数自定义参数,是object类型 42 Console.WriteLine("第{0}个任务的执行结果为:{1}", result.AsyncState, r); 43 } 44 } 45 }
24.异步编程总结
异步编程是建立在委托基础上一种编程的方法
异步调用的每个方法都是在独立的线程中执行,本质上异步编程就是一种多线程程序,是简化的多线程
比较适合在后台运行较为耗时的简单任务,并且任务之间要求相互独立,任务中不应该有直接访问可视化控件的代码
如果后台任务要求必须按照特定顺序执行,或者访问共享资源,则异步编程不太合适,应该选择多线程开发技术
25.线程Thread
进程:一个正在运行的程序就是一个进程,操作系统根据进程分配各种资源(比入内存...)
线程:操作系统为了提高效率会将一个进程分成多个线程,并按照线程来分配CPU执行时间
线程特点:在具有多个CPU的计算机中,可以并行执行
Thread类:表示托管线程,每个Thread对象都代表一个托管线程,每个托管线程都会对应一个函数
ThreadStart 委托定义:public delegate void ThreadStart() 无返回值且无参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private void btn_TaskThread1_Click(object sender, EventArgs e) 2 { 3 //ThreadStart 委托类型 返回值void且无参数 4 int a = 0; 5 Thread objThread1=new Thread(delegate() 6 { 7 for (int i = 1; i <= 10; i++) 8 { 9 a += i; 10 Console.WriteLine("-----1线程------{0}---", a); 11 Thread.Sleep(500); 12 } 13 }); 14 objThread1.IsBackground = true; 15 objThread1.Start(); 16 } 17 18 private void btn_TaskThread2_Click(object sender, EventArgs e) 19 { 20 Thread objThread2 = new Thread(() => 21 { 22 for (int i = 1; i < 100; i++) 23 { 24 Console.WriteLine("-----2线程------{0}---", i); 25 Thread.Sleep(20); 26 } 27 }); 28 objThread2.IsBackground = true; 29 objThread2.Start(); 30 }
26.跨线程访问控件
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private void btnExecute1_Click(object sender, EventArgs e) 2 { 3 Thread objThread1 = new Thread(() => 4 { 5 for (int i = 1; i < 11; i++) 6 { 7 if (lblResult1.InvokeRequired) 8 { 9 lblResult1.Invoke(new Action<string>(s => lblResult1.Text = s), (i * i).ToString()); 10 Thread.Sleep(100); 11 } 12 } 13 }); 14 objThread1.IsBackground = true; 15 objThread1.Start(); 16 } 17 18 private void btnExecute2_Click(object sender, EventArgs e) 19 { 20 Thread objThread2 = new Thread(() => 21 { 22 for (int i = 11; i < 21; i++) 23 { 24 if (lblResult2.InvokeRequired) 25 { 26 lblResult2.Invoke(new Action<string>(s => { lblResult2.Text = s; }), (i * i).ToString()); 27 Thread.Sleep(200); 28 } 29 } 30 }); 31 objThread2.IsBackground = true; 32 objThread2.Start(); 33 }
27.制作二次开发的控件
1.新建项目——>选择类库——>删除自生成的class1——>添加组件——>进入代码视图——>将ComPonent直接替换成TextBox——>引用添加System.Windows.Forms——>using命名空间——>添加一个ErrorProvider——>编写public的验证是否为空的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 判断文本框输入是否为空 3 /// </summary> 4 /// <returns>返回值【0为空】【1不为空】</returns> 5 public int BeginCheckEmpty() 6 { 7 if (this.Text.Trim() == "") 8 { 9 errorProvider.SetError(this, "必填项不能为空!"); 10 return 0; 11 } 12 else 13 { 14 errorProvider.SetError(this,string.Empty); 15 return 1; 16 } 17 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /// <summary> 2 /// 判断文本框输入是否为正整数 3 /// </summary> 4 /// <param name="regularExpression">正则表达式</param> 5 /// <param name="errorMsg">匹配失败后返回的消息</param> 6 /// <returns>返回值【0匹配失败】【1匹配成功】</returns> 7 public int BeginCheckRegularExpression(string regularExpression, string errorMsg) 8 { 9 if (BeginCheckEmpty() == 0) 10 { 11 return 0; 12 } 13 Regex objRegex = new Regex(regularExpression, RegexOptions.IgnoreCase); 14 if (objRegex.IsMatch(this.Text.Trim())) 15 { 16 errorProvider.SetError(this, string.Empty); 17 return 1; 18 } 19 else 20 { 21 errorProvider.SetError(this, errorMsg); 22 return 0; 23 } 24 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 private void btn_Save_Click(object sender, EventArgs e) 2 { 3 int a = STB_Name.BeginCheckEmpty(); 4 int b = STB_Age.BeginCheckRegularExpression(@"^[1-9]d*$", "年龄必须为正整数!"); 5 int result = a * b; 6 if (result!=0) 7 { 8 MessageBox.Show("保存成功"); 9 } 10 }