委托、事件、泛型
委托一
1.定义委托:
public delegate int AddDel(int a, int b);
2.实例该委托:
先准备一个方法,该方法跟该委托的标签要求一样
//定义一个符合委托标签的方法
static int AddDemoFun(int a,int b)
{
return a+b;
}
然后创建一个该委托的对象
// 创建一个委托对象
AddDel delDemo=new AddDemo(AddDemoFun);
3.给该委托对象增加一个方法
//在定义一个方法
public int AddFun (int a,int b)
{
return a*b;
}
//多播委托
Program p= new program();
delDemo +=p.AddFun;
4.输出结果,使用var隐式推断出结果的类型
//使用委托实例
var result=delDemo(3,4);
Console.WritLine(result);
注意:调用多播委托执行的时候,委托里面所有的方法都会被执行,而最后的输出结果则是最后一个方法执行的结果。
委托二
理解委托
定义:
public delegate void MyDelegate(string s);
注:红色部分为方法签名。Void表示方法返回值类型,MyDelegate表示方法名,(string s)表示方法的参数。
委托就是存放 符合某种方法签名的 方法的地址 的集合。
可以简单的将委托理解为地址的集合,只不过这些地址指向的数据是方法而已。
需要注意的是,委托是一种数据类型,就像int、string一样。我们也可以实例化一个委托对象。
MyDelegate myDel;
也就是说对于public delegate void MyDelegate(string s);
MyDelegate是我们自己定义的委托类型。
myDel是该委托类型实例化的对象。
delegate是关键字。
如果你以前接触过C语言,大可以将委托理解为:
一种特殊的指针集合,只不过是方法的指针,仅此而已。
看看Reflector
我们通过Reflector去看看委托到底做了些什么吧~~~
1 delegate 编译后会变成一个class,继承于
MulticastDelegate(多播委托)。
2 delegate 继承于 ——> MulticastDelegate 继承于
——>Delegate。
由此可见delegate在C#中其实本质就是class。
也就是说定义一个委托,其实就是定义一个class罢了。
而用定义好的委托实例化一个对象,也就像实例化一个class类的对象一样。
语法糖特性
例:
public delegate void MyDelegate(string s);
MyDelegate mDel = M1();
其实编译器帮我们补全了,new了委托的对象,并将M1传了进去。
即:MyDelegate mDel = new MyDelegate(M1);
而mDel()调用委托,其本质是mDel.Invoke()
编译器帮我们自动补全了
注:在使用委托之前,最好先判断委托是否为null
委托原理
1、委托实例内部构造
委托对象是由目标对象、方法指针和委托链三部分组成的。其中目标对象存放的是委托方法所在的实例,如上例子中目标对象就是P。方法指针中存放的是该委托实例中的方法地址,委托链是在多播委托的时候用到,里面存放的是其它委托的地址。
2、委托实例化的过程
//创建一个委托对象
AddDel delDemo=new AddDel(AddDemoFun);
当我们new一个委托的时候,就会在内存中创建一个委托对象,该对象里面有目标对象、方法指针和委托链三部分,变量delDemo指向创建的委托对象。
delDemo +=p.AddFun;
当我们向该委托中增加方法时,实际上是又创建了一个委托对象,该对象也是由目标对象、方法指针和委托链三部分组成,而且它的目标对象中存放的是它对应方法所在的对象,方法指针指向它所指向的方法地址。只不过没有具体变量指向该委托,而是在第一次创建的委托的委托链中会产生一个指向该委托对象的变量,把这两个委托关联起来。
当我们再次向第一个委托中增加方法时,在内存中又会创建一个对象,并在第一个的委托链集合中产生一个存放地址的变量使它们关联,如此复始。
什么地方需要用委托:
1 将方法作为参数进行传递
将方法放到一个委托里,再将委托作为参数传入另一个方法。(使用语法糖特性,可以直接将方法传过去,编译器会自动帮我们new一个委托对象,并将方法传入该委托中)
在另一个方法中,调用该委托,其实就是调用委托中的方法。
2 多播委托,一个委托调用多个方法。
3 当该处使用的方法不确定的时候,我们使用一个委托占位置。
微软已经定义好的泛型委托
1 、Action<T>
只要方法无返回值,就可以使用Action这个泛型委托,他是系统定义好了的。
2、 Func<T t1,T t2,TResult r>
最后一个参数表示该委托的返回值
如果方法有返回值,就可以使用Func这个泛型委托。
这些委托是微软已经为我们定义好了的,所以我们可以直接使用这两个委托类型去实例化委托对象。
事件:
简而言之:约束委托,一个私有委托的属性(表述不严谨,谨供理解)
event事件关键字帮我们做了两件事情:
1、创建了一个对应的private的委托对象。
2、创建了一个"事件属性"(其实就类似于属性的set和get),里面包含了add和remove方法,它们都是直接操作上面
的私有委托对象。(本质上就是两个方法)
你在定义该事件的类外部调用该事件,实际上调用的是该事件生成的所有代码中的那个事件属性(本质就是那两个方法)。
你在定义该事件的类内部调用该事件,实际上调用的是该事件生成的所有代码中的那个私有的委托对象,也就是调用了与这个私有委托相关联的所有方法。
所以,使用事件来封装委托,在外部就避免了:
1、直接调用该事件(即直接调用所有相关联的方法)
2、为该事件直接赋值(会冲掉事件中委托相关联的其它方法)
接口声明的关键字是Interface,名字以大写I开头。
当一个类实现接口时,必须把该接口中的所有成员都实现,所以接口中只能有没有方法体方法定义,接口
不能实例化,接口成员不能有访问修饰符,隐士公共public
虽说接口中可以包含属性,方法索引器,事件,但其实这些元素本质都是方法.
例子:这是一个银行账户的接口:
public interface IBankAccount { void PayIn(decimal amount); bool Withdraw(decimal amount);
decimal Balance { get; } } |
如何判断一个类能否继承接口,要看这个类能否有这个功能
例:Bird:Swan,IFly(飞这个功能) 天鹅能飞所以可以继承飞这个接口
Bird:Ostrich,IFly(飞这个功能) 错误因为鸵鸟不能飞
既:实现接口的子类Can do 接口中的行为
什么是接口?
1、接口就是一种规范,协议(*),约定好的遵守某种规范就可以写通用的代码。
2、定义了一组具有各种功能的方法(只是一种功能,没具体实现,像抽象方法一样,光说不练)
使用接口的建议,什么时候用接口?
两个类型的东西无法抽象出同一个父类,但又有相同的行为,这个时候给他们抽象接口,这里建议不要一个接口写过多成员,最好一个接口只实现一个功能
增加灵活性。
在编程时候:接口->抽象类->父类->具体类(在定义方法参数,返回值,声明变量的时候能用抽象就不要用具体)。
接口的继承
接口也可以彼此继承,就象类的继承一样。比如我们又声明一个接口ITransferBankAccount,它继承于IBankAccount接口。
interface ITransferBankAccount : IBankAccount { bool TransferTo(IBankAccount destination, decimal amount); } |
泛型总结:
泛型定义(什么是泛型):
泛型是具有占位符(类型参数)的类、结构、接口和方法,其中占位符也就是类型参数,可以是一个或多个,在占位符的两边加上尖括号:<T>
泛型使用的各种形式:
类型参数与泛型约束:
类型参数一般使用大写字母T,或者以大写字母T开头的,如TKey,TValue,TOutput,TResult等
约束如下:
where T : struct
使用结构约束,类型T必须是值类型
where T : class
类约束指定,类型T必须是引用类型
where T : IFoo
指定类型T必须执行接口IFoo
where T : Foo
指定类型T必须派生于基类Foo
where T : new()
这是一个构造函数约束,指定类型T必须有一个默认构造函数,当new()与其它的约束一起使用的时候,new()放在最后
where T : U
这个约束也可以指定,类型T派生于泛型类型V。该约束也称为裸类型约束
泛型约束举例如下:
public class Person<T, T1, TC, TK, TV, TU>
where T1 : struct //约束T1必须是值类型
where T : class, new()//约束T必须是引用类型,且必须带一个无参构造函数;当new()与其它的约束一起使用的时候,new()放在最后
where TC : new() //这个类型必须带有一个无参数的构造函数【要求:1.构造函数不能为私有,2.类型不能是抽象的。】
where TK : Car //这里约束了TK类型,必须是Car类型或者是Car类型的子类
where TV : IComparable //约束类TV必须是实现IComparable接口的类型。
where TU : T //约束了TU必须是T的子类。或者是T类型。
{ public T Name { get; set; } public T1 Age { get; set; } public T Email { get; set; } public TC MyCar { get; set; } } public class Car { public Car() { this.Brand = "捷安特"; } public Car(string brand) { this.Brand = brand; } public string Brand { get; set; } } |
泛型类
如上例中Person类;类中的方法可以使用泛型类的类型参数;
泛型方法
方法中的参数可以使用泛型参数,来实现方法对于不同类型参数的支持,实现代码重用。
如:
public void Test<T>(T t) { //代码体 } |
泛型集合
ArrayList集合的升级版:List<T>
HashTable的升级版: Dictionary<K,V>
List<T>
Capacity 获取或设置该内部数据结构在不调整大小的情况下能够容纳的元素总数。
Count 获取 List<T> 中实际包含的元素数。
Add 将对象添加到 List<T> 的结尾处。
AddRange 将指定集合的元素添加到 List<T> 的末尾。
Clear 从 List<T> 中移除所有元素。
Contains 确定某元素是否在 List<T> 中。
Remove 从 List<T> 中移除特定对象的第一个匹配项。
RemoveAll 移除与指定的谓词所定义的条件相匹配的所有元素。
RemoveAt 移除 List<T> 的指定索引处的元素。
RemoveRange 从 List<T> 中移除一定范围的元素。
Reverse() 将整个 List<T> 中元素的顺序反转。
Sort() 使用默认比较器对整个 List<T> 中的元素进行排序。
ToArray 将 List<T> 的元素复制到新数组中。
Dictionary<K,V>属性方法:
Comparer 获取用于确定字典中的键是否相等的 IEqualityComparer<T>。
Count 获取包含在 Dictionary<TKey, TValue> 中的键/值对的数目。
Keys 获取包含 Dictionary<TKey, TValue> 中的键的集合。
Values 获取包含 Dictionary<TKey, TValue> 中的值的集合。
Add 将指定的键和值添加到字典中。
Clear 从 Dictionary<TKey, TValue> 中移除所有的键和值。
ContainsKey 确定 Dictionary<TKey, TValue> 是否包含指定的键。
ContainsValue 确定 Dictionary<TKey, TValue> 是否包含特定值。
//KeyValuePairs<K,V>为键值对类型
foreach(KeyValuePairs<K,V> kv in dict) { //... } |
泛型结构 Nullable<T>
[SerializableAttribute]
public struct Nullable<T>
where T : struct, new()
属性:
HasValue 当前Nullable<T>对象有值,则返回true,否者返回false
Value 获取或设置当前Nullable<T>对象的值
可空类型直接取Value值的话,当为null时,会报异常,所以可空类型在获取Value前要判断一下HasValue的值,HasValue为true时,再取值。
泛型接口
使用泛型可以定义接口,接口中的方法可以带泛型参数。
如:IComparer<T>接口
public interface IComparer<T> { // Methods int Compare(T x, T y); } |
泛型委托
public delegate int Comparsion<T>(T x, T y);
系统内置的泛型委托:
Action<T> (无返回值)
Func<T,TResult> (有返回值)
逆变(in)、协变(out)
通常,协变类型参数可用作委托的返回类型,而逆变类型参数可用作参数类型。
协变和逆变统称为“变体”。
泛型委托,泛型接口举例:
写方法对不同数组实现排序:(参照List<T>中Sort<T>方法的实现)
1.使用系统的委托类型 Comparison<T>
static void ArraySort<T>(T[] arr, Comparison<T> comparer) { //对于数组的排序方法,使用Array.Sort<T>来完成,两元素的比较方法由外部导入 Array.Sort<T>(arr, comparer); } |
2.使用自定义的泛型委托 DelegateCompare<T>
排序方法内通过调用Array.Sort<T>(T[] arr,IComparer<T> comparer);来完成,需要一个实现了IComparer<T>接口的类的对象,具体需要定义一个泛型类CompareFormattor,实现IComparer<T>泛型接口,类中对接口的实现具体是靠初始化CompareFormattor时传入的泛型委托来实现的。
//自定义泛型委托 public delegate int DelegateCompare<T>(T x, T y); //排序方法的实现 static void ArraySort<T>(T[] arr,DelegateCompare<T> dg)//参数:数组,委托 { //内部使用Array.Sort()方法,传入一个比较器的重载方法,需要实现IComparer接口的类型 IComparer<T> comparer = new ComparerFormattor<T>(dg); Array.Sort(arr,0,arr.Length, comparer); } //ComparerFormattor<T>类,实现了IComparer<T> public class ComparerFormattor<T>:IComparer<T> { DelegateCompare<T> compareDel; public ComparerFormattor(DelegateCompare<T> compareDg) { this.compareDel = compareDg; } public int Compare(T x, T y) { return compareDel(x, y); } } |
泛型的意义:
1.泛型集合:避免了装箱与拆箱:
使用ArrayList存储int类型数据,ArrayList内部维护的是object类型的数组,所以在int类型数据存储和提取的时候,会发生装箱和猜想的操作,效率低下;而使用List<int>的时候,编译器动态生成int类型的数组来存储,避免发生装箱拆箱的操作。
2.泛型方法:一个方法实现了对不同参数类型的处理,实现了代码重用。