委托和匿名方法和Lambda表达式
委托
- 把一个函数作为参数传递
- 函数可以直接赋值给一个委托对象、委托的签名必须跟函数的签名一样
- 是C#中类型安全的,可以订阅一个或者多个具有相同签名方法的函数指针。
使用委托对的步骤
- 1.声明一个委托类型。类似方法声明,但是没有实现块。
- 2.使用该委托类型声明一个委托变量。
- 3.创建委托类型的对象,并把它赋值给委托变量。新的委托对象包括指向某个方法的引用,这个方法和第一步定义的签名和返回类型一致。
- 4.可以选择为委托方法增加其它方法。方法必须与定义的委托类型有相同的签名和返回类型。
- 5.在代码中可以像调用方法一样调用委托。在调用委托时,其包含的每一个方法都会被执行。
//声明委托对象
delegate void MyDel(int value);
class Program
{
static void Main(string[] args)
{
Program program = new Program();
//声明委托变量
MyDel del;
//创建随机整数生成器对象,并得到0到99之间的一个随机数
Random rand = new Random();
int randomValue = rand.Next(99);
//创建一个包含PrintLow 或者PrintHigh 的委托对象,并赋值给del变量
del = randomValue < 50
? new MyDel(program.PrintLow)
: new MyDel(program.PrintHigh);
del(randomValue);
Console.ReadKey();
//某次输出:38-Low Value 30-Low Value 92-High Value
}
void PrintLow(int value)
{
Console.WriteLine("{0}-Low Value", value);
}
void PrintHigh(int value)
{
Console.WriteLine("{0}-High Value", value);
}
}
比较创建类与使用类和委托的过程
null | 类 | 委托 |
---|---|---|
声明类型 | 声明类 | 声明委托(类型) |
声明类型的变量 | 声明类类型的变量 | 声明委托类型的变量 |
填充变量 | 创建类的实例并且把它的引用赋值给变量 | 创建委托的实例并且把它的引用赋值给变量,然后增加第一个方法 |
使用变量 | 使用类对象 | 调用委托对象 |
- 委托保存的方法可以来自任何类或结构,只有委托的返回类型和签名匹配
- 方法可以是静态方法,也可以是实例方法
- 调用委托时,会执行其调用列表的所有方法
组合委托
MyDel delA = Method1;
MyDel delB = Method2;
//组合调用列表
MydDel delC = delA + delB;
- 委托是恒定的,委托对象被创建后不能再被改变。
委托的添加方法和移除方法
- 添加:+= 运算符
- 实际是创建了一个新的委托
- 移除:-= 运算符
- 实际是创建了一个新的委托
- 若在调用列表中的方法有多个实例,-=运算符将从列表最后开始搜索,并且移除第一个与方法匹配的实例。
- 试图删除委托中不存在的方法没有效果
- 试图调用空委托会抛出异常。
委托示例
//定义一个没有返回值和参数的委托类型
delegate void PrintFunction();
class Program
{
static void Main(string[] args)
{
//创建一个测试类的实例
Test t = new Test();
//创建一个空委托
PrintFunction pf;
//实例化并初始化该委托
pf = t.Print1;
//给委托增加3个另外的方法
pf += Test.Print2;
pf += t.Print1;
pf += Test.Print2;
//现在委托有4个方法
//确认委托有方法
if(null!=pf)
{
//调用委托
pf();
}
else
{
Console.WriteLine("Delegate is empty!");
}
Console.ReadKey();
//输出:
//Print1--instance
//Print2--static
//Print1--instance
//Print2--static
pf -= Test.Print2;
if (null != pf)
{
//调用委托
pf();
}
else
{
Console.WriteLine("Delegate is empty!");
}
Console.ReadKey();
//输出:
//Print1--instance
//Print2--static
//Print1--instance
pf -= Test.Print2;
pf -= t.Print1;
pf -= Test.Print2;
pf -= t.Print1;
if (null != pf)
{
//调用委托
pf();
}
else
{
Console.WriteLine("Delegate is empty!");
}
Console.ReadKey();
//输出:Delegate is empty!
}
}
class Test
{
public void Print1()
{
Console.WriteLine("Print1--instance");
}
public static void Print2()
{
Console.WriteLine("Print2--static");
}
}
调用带返回值的委托
- 若委托有返回值并且在调用列表中有一个以上的方法,会发生以下的情况:
- 调用列表中最后一个方法返回的值就是委托调用返回的值。
- 调用列表中所有其它方法的返回值都会被忽略。
//声明有返回值的方法
delegate int MyDel();
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
//创建并初始化委托
MyDel myDel = mc.Add2;
//增加方法
myDel += mc.Add3;
myDel += mc.Add2;
Console.WriteLine("Value:{0}", myDel());
Console.ReadKey();
}
//输出:Value:12
}
class MyClass
{
int IntValue = 5;
public int Add2()
{
IntValue += 2;
return IntValue;
}
public int Add3()
{
IntValue += 3;
return IntValue;
}
}
myDel();---> Add2();返回值7被忽略
--->Add3();返回值10被忽略--->Add2();使用返回值12
调用带引用参数的委托
- 若委托有引用参数,参数值会根据调用列表中的一个或者多个方法的返回值而改变
- 在调用委托列表中的下一个方法时,参数的新值(不是初始值)会传给下一个方法。
delegate void MyDel(ref int x);
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
MyDel myDel = mc.Add2;
myDel += mc.Add3;
myDel += mc.Add2;
int x = 5;
myDel(ref x);
Console.WriteLine("Value:{0}", x);
Console.ReadKey();
//输出:Value:12
}
}
class MyClass
{
public void Add2(ref int x)
{
x += 2;
}
public void Add3(ref int x)
{
x += 3;
}
}
myDel();---> Add2(x=5);引用x的初始值
--->Add3(x=7);引用x新的输入值--->Add2(x=10);引用x新的输入值
匿名方法
- 若方法只会被使用一次,除创建委托的语法需要,没有必要创建独立的具名方法。
- 匿名方法是在初始化委托时内联声明的方法。
//命名方法
delegate int OtherDel(int InParam);
class Program
{
public static void Add20(int x)
{
return x + 20;
}
static void Main()
{
OtherDel del = Add20;
Console.WriteLine("{0}",del(5));
Console.WriteLine("{0}",del(6));
Console.ReadKey();
}
//输出:25 \n 26
}
//匿名方法
delegate int OtherDel(int InParam);
class Program
{
static void Main()
{
OtherDel del = delegate(int x)
{
return x + 20;
};
Console.WriteLine("{0}",del(5));
Console.WriteLine("{0}",del(6));
Console.ReadKey();
}
//输出:25 \n 26
}
使用匿名方法
- 1.声明委托变量时作为初始化表达式
- 2.组合委托时在赋值语句的右边
- 3.为委托增加事件时在赋值语句的右边
匿名方法的语法
delegate + (参数列表) + 语句块
返回类型
- 匿名方法的返回类型和委托的返回类型一致
- 例如:委托的返回类型是int ,则匿名方法的实现代码也必须在代码路径中返回int
//委托类型的返回类型为int
delegate int OtherDel(int InParam);
static void Main()
{
OtherDel del = delegate(int x){
return x + 20;//返回一个int
};
}
参数
- 除了参数数组,匿名方法的参数列表必须在参数数量参数类型及位置修饰符方面与委托匹配
- 在满足委托的参数列表不包含任何out参数和匿名方法不使用任何参数的情况下,可以通过使用圆括号为空或省略圆括号来简化匿名方法的参数列表。
//声明委托类型
delegate void SomeDel(int x);
SomeDel SDel = delegate{//省略参数列表
PrintMessage();
Cleanup();
};
params参数
- 若委托声明的参数列表包含了params参数,则匿名方法的参数列表将忽略params关键字
delegate void SomeDel(int x,params int[] y);
SomeDel SDel = delegate(int x,int[] y)
{
...
};
Lambda表达式
匿名方法转换为Lambda表达式的步骤
- 1.删除delegate关键字
- 2.在参数列表和匿名方法主体之间放Lamdba运算符=>。读作“goes to”
//匿名方法
MyDel del = delegate(int x) {return x + 1;};
//Lambda表达式
MyDel del = (int x)=>{return x + 1;};
- 编译器通过推断可以进一步简化Lambda表达式
//1.省略类型参数
MyDel del = (x)=>{return x+1;};
//2.只有一个隐式参数类型,省略圆括号
MyDel del = x=>{return x+1;};
//3.若语句块包含了一个返回语句,则可将语句块替换为return后的表达式
MyDel del = x=> x+1;
- 参数列表要点
- 1.必须在参数数量、类型和位置上与委托相匹配
- 2.表达式的参数列表中的参数不一定需要包含类型(隐式类型),除非委托有ref或者out参数,此时必须注明类型(显示类型)
- 3.如果只有一个参数,并且是隐式类型的,周围的圆括号可以被省略,否则必须有括号。
- 4.若没有参数,必须使用一组空的圆括号。
(参数,参数,) =>{语句}表达式
(参数) =>{语句}表达式
参数 =>{语句}表达式
() =>{语句}表达式