今日学习了:委托、事件、匿名函数、Lambda表达式、闭包概念
1.委托
可以理解为存储函数的变量类型
本质是一个类,用来定义函数的类型,不同的函数必须对应和各自“格式”一致的委托
关键字:delegate
基本语法:
访问修饰符 delegate 返回值 委托名 (参数列表);
可以申明在namespace或class语句块中
快速记忆:函数申明的语法前面加一个delegate关键字
访问修饰符不写默认为public,在别的namespace命名空间中也能使用
private在其他命名空间中无法使用,一般用public
复习访问修饰符:
调用方的位置 | public | protected internal | protected | internal | private protected | private | file |
---|---|---|---|---|---|---|---|
在文件中 | ✔️️ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
在类内 | ✔️️ | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ |
派生类(相同程序集) | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ | ❌ |
非派生类(相同程序集) | ✔ | ✔ | ❌ | ✔ | ❌ | ❌ | ❌ |
派生类(不同程序集) | ✔ | ✔ | ✔ | ❌ | ❌ | ❌ | ❌ |
非派生类(不同程序集) | ✔ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
如何定义委托?
例如申明一个可以用来存储无参无返回值函数的容器
这里只是定义了规则 并没有使用
delegate void MyFun();
//delegate int MyFun(int a); 委托不存在重载的规则,不允许重名
委托就是专门用来装相同格式函数的容器
如何使用和调用:
第一种方式
MyFun f = new MyFun(fun);//存入的函数一定要与定义的委托格式匹配,无论是返回值还是参数列表
f.Invoke();//第一种调用方式
第二种方式
MyFun f2 = fun;//本质上跟第一种一样,只是简化了
f2();//第二种调用方式
MyFunc f3 = fun2;
Console.WriteLine(f3.Invoke(3)+" "+f3(5));//两种调用
委托一般在哪里用?
1.常作为类的成员
2.作为函数的参数
class Test
{
public MyFun fun=null;
public MyFunc fun2=null;
public void TestFun(MyFun _fun,MyFunc _fun2)
{
//先做一些其他的逻辑,当这些逻辑处理完了 再执行传入的函数
//延迟执行的逻辑
this.fun += _fun;
this.fun2 += _fun2;
fun();
Console.WriteLine( fun2(0));
}
}
委托变量可以存储多个函数(多播委托)
加+:
MyFun ff = fun;
ff += fun1;//ff中存入匹配格式的fun函数和fun1函数
ff();//按顺序依次执行fun、fun1函数
//也可以这样写
ff = null;
ff += fun;
ff += fun1;
ff();
减-:
//从容器中移除指定的函数
ff -= fun;
ff();
ff -= fun1;
//ff();委托类型为空时运行会报错!!!
所以一般需要先判空
if (ff != null) ff();
ff -= fun1;//多减不会报错,不做处理
系统提供的委托 使用时需要引用System命名空间
Action action = null;//系统自带无参无返回值类型委托Action
action += fun;
action();
Func<string> funcstring = fun4;//系统自带无参有指定类型返回值的泛型委托Func<T>
Func<int> funcint = fun5;
委托是支持 泛型的 可以让返回值和参数类型可变化
Action<int, string> action1;//系统自带无返回值可以传n个参数的委托Action<T,K,M,Z...>(T t,K k,M m,Z z...)可以传0~16个参数
Func<int, string> func=fun4;//系统自带可以传n个参数有返回值的委托,最多可以传16个参数,最后一个参数为返回值
Console.WriteLine( func(2));
//注意如果类中函数存在重载,委托可以根据匹配格式以及函数名却确定函数具体的返回值和参数类型
2.事件
事件是什么?
事件是基于委托的存在
事件是委托的安全包裹
让委托的使用更具有安全性
类似属性,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你在声明事件时使用的访问符相同。
申明语法:
访问修饰符 event 委托类型 事件名;
//事件成员变量存储函数
public event Action myEvent;
事件的使用:
1.事件是作为 成员变量存在于类中
2.委托怎么用 事件就怎么用
事件的使用与委托一模一样
myEvent = TestFun;
myEvent += TestFun;
myEvent();
myEvent -= TestFun;
myEvent = null;
事件不同于委托的区别
1.不能在类外部 赋值
事件是不能在外部赋值的
//t.myEvent = null;
//t.myEvent=t.TestFun;
委托可以在类外部调用
t.myFun.Invoke();
2.不能在类外部 调用
事件不能在类外部调用
//t.myEvent.Invoke();
//只能在类的内部去封装调用
3.不能作为函数的临时变量在函数内部使用
而委托可以在函数里随意使用
4. + -的访问限定与你在声明事件时使用的访问符相同。
//如果申明的是public,+-在外部是可以使用的。
t.myEvent += t.TestFun;
为什么有事件?
1.防止外部随意置空委托
2.防止外部随意调用委托
3.事件相当于对委托进行了依次封装,使得其更安全
它只能作为成员存在于类和接口以及结构体中,更加安全
总结:
事件和委托的使用几乎一模一样,事件是特殊的委托
事件不能在外部使用赋值调用,最多只能用+= 、 -=来移除和添加指定函数
事件在外部就算是 t.myEvent=t.myEvent+TestFun,也是会报错的,事件会认为这是在赋值,不等同于+=
委托不管在哪里都能赋值调用
事件不能当临时变量类型在函数中使用
3.匿名函数
什么是匿名函数?
没有名字的函数
匿名函数的使用是配合委托和事件进行使用
脱离委托和事件 无法使用
基本语法
delegate (参数列表)
{
函数逻辑
};
1.无参无返回 申明
Action a = delegate ()
{
Console.WriteLine("无参无返回匿名函数");
};
a();
2.有参无返回 申明
Action<int,string> b = delegate (int a, string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
};
b(1,"维尼");
3.无参有返回值 申明
Func<string> f = delegate ()
{
return "小熊";//返回什么类型可以自动识别
};
Console.WriteLine (f());
4.有参有返回值 申明
Func<int,int,string> ff= delegate (int x, int y)
{
return "哈希";
};
Console.WriteLine( ff(1, 2));
何时使用?
class Test
{
public Action action;
public void Dosomething(int a,Action fun)
{
Console.WriteLine(a);
fun();
}
public Action getFun()
{
//2.作为返回值
return delegate () {
Console.WriteLine("函数内部返回的无参无返回的匿名函数逻辑");
};
}
}
1.函数中传递委托参数时
//参数传递
Test t = new Test();
Action ac = delegate ()
{
Console.WriteLine("随参数传入的匿名函数逻辑");
};
t.Dosomething(100, delegate ()
{
Console.WriteLine("随参数传入的匿名函数逻辑");
});
t.Dosomething(50, ac);
2.委托或事件赋值时
public Action getFun()
{
//作为返回值
return delegate () {
Console.WriteLine("函数内部返回的无参无返回的匿名函数逻辑");
};
}
匿名函数的缺点
//添加到委托或者事件容器中后 不记录 无法单独移除 只能清空,才能将其移除
Program p = new Program();
p.ac += delegate ()
{
Console.WriteLine("匿名函数一");
};
p.ac += delegate ()
{
Console.WriteLine("匿名函数二");
};
p.ac();
p.ac -= delegate ()//此匿名函数非彼匿名函数,无法通过相同的逻辑来指定匿名函数,这里是无法删除之前的匿名函数
{
Console.WriteLine("匿名函数一");
};
p.ac();
总结
匿名函数 就是没有名字的函数
固定写法
delegate(参数列表){}
主要是在 委托传递和存储时为了方便可以使用匿名函数
缺点是没有办法指定移除
//拓展 事件只是在类内部的一个委托容器而已,不会作为参数存在
4.Lambda表达式
什么是Lambda表达式
可以将 Lambda表达式 理解为匿名函数的简写
它除了写法不一样
使用上和匿名函数一模一样
都是配合委托和事件 配合使用
lambda表达式
(参数列表) =>
{
函数体
};
1.无参无返回值
Action a = () =>
{
Console.WriteLine("无参无返回值Lambda表达式");
};
a();
2.有参无返回值
Action<int> b =(int val) =>
{
Console.WriteLine("有参{0}无返回值Lambda表达式",val);
};
b(10);
3.甚至参数类型都可以省略
Action<int> c = (val) =>
{
Console.WriteLine("省略参数类型的有参{0}无返回值Lambda表达式", val);
};
c(10);
4.有返回值
Func<string,int> fun = (val) =>
{
Console.WriteLine("有参有返回值的Lambda表达式{0}", val);
return 1;
};
Console.WriteLine(fun("小熊维尼一个"));
其他传参使用和匿名函数一样,缺点也跟匿名函数一样
核心:闭包
内层的函数可以引用包含它外层的函数的变量
即使外层函数的执行已经终止
注意:
该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值
class Test
{
public event Action action;
public Test() {
int value = 10;//value的生命周期改变,除非将action手动置空,否则不会被释放
//这里构成闭包
//因为当构造函数执行时,临时变量value的生命周期改变了
action = () =>
{
Console.WriteLine(value);
};
for(int i = 0; i < 10; i++)
{
action += () =>
{
Console.WriteLine(i);//这里添加的十个lambda表达式的i都会保存为执行完循环的最终值,也就是10
};
}
//如果你想要打印0~9怎么写呢?
for(int i = 0; i < 10; i++)
{
int index = i;
action += () =>
{
Console.WriteLine(index);//每次循环都声明新的临时变量index,相当于存入10个不同的临时变量分别为0~9
};
}
}
public void Dosomething()
{
action.Invoke();
}
}
class Program
{
//有一个函数,返回一个委托函数,这个委托函数中只有一句打印代码
//之后执行返回的委托函数时,可以打印出1~10
public static void Main(string[] args)
{
getFun1()();
getFun2()();
getFun3()();
}
static Action getFun1()
{
Action a = null;
for(int i = 1; i <= 10; i++)
{
int index = i;
a += () =>
{
Console.WriteLine(index);
};
}
return a;
}
static Action getFun2()
{
Action a = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
a += delegate()
{
Console.WriteLine(index);
};
}
return a;
}
static Action getFun3()
{
Action a = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
a += ()=>
{
Show(index);
};
}
return a;
}
static void Show(int i)
{
Console.WriteLine(i);
}
}
总结
匿名函数的特殊写法 就是 Lambda表达式
固定写法 ()=>{}
参数列表 可以直接省略参数类型
主要在 委托传递和存储时 为了方便可以直接使用匿名函数或者lambda表达式
缺点 无法被指定删除
关于委托的补充知识
public static void Main(string[] args)
{
Func<string> funTest = () =>
{
Console.WriteLine("第一个函数");
return "1";
};
funTest += () =>
{
Console.WriteLine("第二个函数");
return "2";
};
funTest += () =>
{
Console.WriteLine("第三个函数");
return "3";
};
Console.WriteLine(funTest());//这里只会返回3
//如果想获取到每一个函数执行后的返回值
//通过GetInvocationList方法获取到委托列表
//然后通过迭代器遍历获取每一个函数单独执行单独获取
foreach(Func<string> item in funTest.GetInvocationList())
{
Console.WriteLine(item());
}
}