C#笔记[ 十一 ]:委托,事件,Lambda,Linq

Evernote Export

 

 
.................................. C#的委托.................................
委托
是特殊的密封类,不能被继承;代表了某一种相同签名方法的有序集合 [有序集合:多播委托]
 
如果说string代表了所有的字符串,那么 delegatestringMyDelegete(int a); 代表了签名为输入为string类型,输出为一个参数且类型为int的所有方法
作用
1. 类似于 C语言 中的函数指针,将方法当作参数传入另一个方法的参数,来提高扩展性
2. 异步调用:把委托塞进线程池里,过一段时间再来读取数据,如果委托未完成,会阻塞线程,等待委托完成
3. 异步回调:把委托完成后需要的操作编写成一个形参为委托类型的回调函数,等委托完成后,自动执行回调函数
定义
跟定义方法一样 [public/internal] delegate 数据类型[int,void] 方法名(); [ 所以没有什么成员 ]
使用
跟实例类一样
定义声明委托 ---> 实例化委托[ 传递方法名 ] ---> 调用委托[ 传递方法参数,有序调用委托里面的 所有的方法 ]
 
委托有三种实例方法
1.同步调用:Invoke(方法参数);赋于同步访问的能力
 
2.异步调用:IAsyncResult异步接口
BeginInvoke(方法参数, 回调函数, 回调函数执行的反馈) / EndInvoke(IAsyncResult异步接口);赋于异步访问的能力
 
3.异步回调:IAsyncResult异步接口
BeginInvoke(方法参数, 回调函数, 回调函数执行的反馈) / EndInvoke(IAsyncResult异步接口);赋于异步访问的能力
回调函数:要在回调函数中获取委托的值,需要用到 AsyncResult线程返回值类
 
内置委托
1.Action : 无参数,无返回值的方法
2.Action<> : 至少有1个泛型参数代表方法参数,无返回值的泛型委托
3.Func<> : 至少有1个泛型参数代表方法返回值,最后一个泛型代表返回值,有返回值的泛型委托
4.Predicate<>: 有且只有一个泛型参数,有返回值且返回值是 bool型的泛型委托
5.EventHandler<object ,T>: 泛型委托,两个参数,一个是object,一个是 泛型T且必须继承自基类EventArgs,无返回值
 
 
实例化委托有三种情况,可以用于[同步调用,异步调用,异步回调];下面三种情况都是同步调用,
 
 
----------------------------------------------------------------
publicdelegatevoidMyDelegate();
classTest
{
publicvoidSayHello()
{
Console.WriteLine("Hello Delegate!");
}
publicstaticMyDelegateSayHi() // 定义一个委托类型的方法,return 委托对象
{
MyDelegate m = () => // 使用 Lambda 表达式创建匿名方法,赋值给委托变量
{
Console.WriteLine("Hi");
};
return m;
}
}
// 第一种方式:方法是实例方法,使用匿名对象的方式 [ new Test( ) 返回一个Test类的对象,叫做 匿名对象 ]
MyDelegate myDelegate = newMyDelegate(newTest().SayHello);
 
// 第二种方式:用 Lambda表达式
MyDelegatemyDelegate3 = newMyDelegate((x, y) => x + y);
 
// 第三种方式:用 匿名方法
委托名 委托对象 = delegate
{
 
};
 
publicdelegateintMethodDelegate(int x, int y);
 
MethodDelegate method = delegate(int x, int y)
{
return x+y;
};
Console.WriteLine(methodDelegate(1, 2));
 
// 第四种方式:使用隐式转换的方式
MyDelegate myDelegate2 = Test.SayHello();
 
myDelegate();
 
--------------------------------------------------------------------------------
 
 
委托的作用_1:用其他形参传递方法参数,用委托变量作为形参传递方法名,委托类型=委托形参的类型=传入方法的类型;提高扩展性
就是我们平时的函数调用,都是同步调用;会阻塞线程,执行完调用后,再继续执行其他的操作
publicdelegatedoubleCalcuateDelegate(double a, double b);
 
staticvoidMain(string[] args)
{
Console.WriteLine("Result:{0}", Calcuate(1, 2, Add));
Console.WriteLine("Result:{0}", Calcuate(1, 2, Multiply));
Console.ReadKey();
}
 
publicstaticdoubleCalcuate(double a, double b, CalcuateDelegate d)
{
returnd(a, b);
}
publicstaticdoubleAdd(double a, double b)
{
Console.WriteLine("一些其他的操作。。。")
return a + b;
}
publicstaticdoubleSubtract(double a, double b)
{
return a - b;
}
publicstaticdoubleMultiply(double a, double b)
{
return a * b;
}
// ........ 以后可以不断加方法
 
--------------------------------------------------------------------------------
 
publicdelegateintAddDelegate(int a, int b);
publicclassAddClass
{
publicstaticintAdd(int a, int b)
{
Console.WriteLine("开始计算:" + a + "+" + b);
Thread.Sleep(3000); //模拟该方法运行三秒
Console.WriteLine("计算完成!");
return a + b;
}
}
 
委托的作用_2:异步调用:不阻塞线程,而是把调用塞到线程池中,主线程继续执行其他操作,过一段时间后再来获取异步调用的值
实现:调用通过"异步结果类",委托的"BeginInvoke()"方法调用委托来实现异步,"EndInvoke()"来获取结果,但是会令主线程进入阻塞状态
坏处:可能过一段时间再来读取"异步调用"的结果时,操作还没完成,主线程也要等待
//异步调用----------------------------
Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
AddDelegate handler2 = new AddDelegate(AddClass.Add);
 
//IAsyncResult: 异步调用接口(interface)
//BeginInvoke: 委托(delegate)的一个异步方法的开始
IAsyncResult result2 = handler2.BeginInvoke(1, 2, null, null); // 异步操作开始
 
Console.WriteLine("继续做别的事情。。。");
Thread.Sleep(3000); // 主线程做其他的事情花了3秒后看看是否计算完成
 
Console.WriteLine(handler2.EndInvoke(result2)); // 获取异步操作的结果
Console.ReadKey();
 
// ===== 异步调用 AsyncInvokeTest =====
// 继续做别的事情
// 开始计算: 1+2
// 计算完成!
// 3
 
委托的作用_3:异步回调:因为"异步调用的EndInvoke()"还是会阻塞主线程,所以 通常创建 "回调函数"
当委托完成后,"回调函数"自动执行,告诉主线程委托完成了,主线程就可以不管它专心做其他事情
//异步回调------------------------------
publicstaticvoid回调函数(IAsyncResult iar)
{
//执行回调方法,是因为委托已经执行完成了
// 1. IAsyncResult iar:不包含委托的返回值,是 EndInvoke()方法包含,
// 2. 但是可以通过 AsyncResult.AsyncDelegate 属性来复制当前正在运行的委托,然后调用克隆委托的 EndInvoke() 来获取结果
AsyncResult ar = (AsyncResult)iar;
var anotherDelegate = (IsPrimeNumberDelegate)ar.AsyncDelegate;
var ret = anotherDelegate.EndInvoke(iar);
Console.WriteLine(ret); // 异步回调结束
}
 
Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
AddDelegate handler3 = new AddDelegate(AddClass.Add);
IAsyncResult result3 = handler.BeginInvoke(1,2,newAsyncCallback(回调函数), "AsycState:OK"); // 异步回调开始
 
Console.WriteLine("继续做别的事情。。。");
Console.ReadKey();
 
// ===== 异步回调 AsyncInvokeTest =====
// 继续做别的事情
// 开始计算: 1+2
// 计算完成!
// 3
// AsycState:OK!
 
--------------------------------------------------------------------------------
 
多播委托[ 委托的组播 ]:包含多个方法的委托
多个委托可以形成 委托链;你可以用 [ 赋值运算符(+=)附加方法,(-=)删除方法 为委托链 ]
此时如果调用多播委托,将会顺序的执行委托链上所有的函数,前面的返回值会被后面的覆盖,只会返回最后一个委托的返回值。
如果某个函数出现了异常,则其后所有的函数都不会执行
 
delegateintNumberChangerDelegate(int n);
classTestClass
{
staticvoidMain(string[] args)
{
NumberChangerDelegate nc;
NumberChangerDelegate nc1 = newNumberChangerDelegate(AddNum);
NumberChangerDelegate nc2 = newNumberChangerDelegate(MultNum);
nc = nc1;
nc += nc2;
nc(5);
}
}
 
 
 
 

 
 
.................................. C#已定义委托,已定义泛型委托................................
 
委托需要先声明一个类型才能使用,这很麻烦
比如我在某一个命名空间声明了一个没有返回值的委托myDelegate,然后想要这个委托的时候,每个命名空间都要引用这个命名空间,这太不友好了
微软直接就在框架 System 命名空间中定义了四种委托:Action、 Action<>、 Func<>、 Predicate<> ---> 实例化该委托 ---> 调用该委托 就不再需要使用 delegate关键字声明委托了
 
Action:方法类型 => 无参数,无返回值的委托
构造函数:publicdelegate void Action();
 
// 无参数,无返回值的方法
Action a1 = ( ) => Consoles.WriteLine(1);
a1( );
 
----------------------------------------------------------------
 
Action<T>:方法类型 => 1个或多个泛型参数,无返回值的泛型委托
构造函数:publicdelegate void Action<inT>(T arg); // T:表示泛型的数据类型 arg:表示是这个数据类型的参数
publicdelegatevoidAction<inT1, inT2>(T1 arg1, T2 arg2); // T1,T2: 表示泛型的数据类型 arg1,arg2:表示这个数据类型的参数
...... 还有很多这样的构造函数,所以可以有一个或多个参数;因为是泛型,所以数据类型是任意的; 因为都是void:所以都是无返回值
 
//有一个参数,参数类型是 int;且无返回值的方法
Action<int> a2 = (x) => MessageBox.Show( x.ToString( )); // 使用 Lambda 表达式的方法实例化委托
Action<int> a2 = delegate { MessageBox.Show( x.ToString( )); }; // 使用 匿名方法 表达式的方法实例化委托
a2(1);
 
//有两个参数,参数类型是 int,string;且无返回值的方法
Action<int, string> a3 = (x, y) => Consoles.WriteLine( (x+1).ToString + y );
a3(3, "Hello");
 
----------------------------------------------------------------
 
Func<T>:方法类型 => [ 1个或多个泛型参数,最后一个泛型代表返回值,有返回值的泛型委托]
构造函数:publicdelegate TResult Func<outTResult>(); // TResult:表示返回值的类型 ():小括号表示没有参数
publicdelegateTResultFunc<inT, outTResult>(T arg); // TResult:表示返回值的类型 T:表示泛型的数据类型 arg:表示是这个数据类型的参数
publicdelegateTResultFunc<inT1, inT2, outTResult>(T1 arg1, T2 arg2);
...... 还有很多这样的构造函数,所以至少有一个泛型表示返回值的类型,且放在最后;其他参数的类型放在它之前
 
// 无参数,返回值类型是 int 的方法
Func<int> fun1 = () => 1; // 使用 Lambda 表达式的方法实例化委托
Func<int> func8 = delegate { return1; }; // 使用 匿名方法 表达式的方法实例化委托
var value1 = fun1();
 
// 有一个参数,参数类型是 int,返回值类型是 string 的方法
Func<int, string> fun2 = (x) => (x+10).ToString(); // 使用 Lambda 表达式的方法实例化委托
Func<int, int> func7 = delegate(int x) { return x + 1; }; // 使用 匿名方法 表达式的方法实例化委托
var value2 = fun2(10);
 
----------------------------------------------------------------
 
Predicate<>:方法类型 => 有且只有一个泛型参数,有返回值且返回值是 bool型的泛型委托
public delegate bool Predicate<inT>(T obj);
 
// 定义方法类型 => 有且只有一个参数,有返回值,且返回值是 bool
Predicate <int> p1 = (x) => x > 0;
bool value1 = p1(3);
 
 
----------------------------------------------------------------
EventHandler(object sender, EventArgs e): .Net自带的一个委托,它的方法一般都是 "事件"
publicdelegatevoidEventHandler(object sender, EventArgs e);
 
// object sender:当前的对象;例如button的点击事件,那么这个sender就代表这个button自己
// EventArgs e:记录事件传递过来的额外信息;Click事件[ 用户点击的位置 ];KeyDown事件[键盘按下的键等事件的额外信息 ]
 
privatevoidtextBox1_KeyDown(object sender, KeyEventArgs e)
{
if(e.KeyCode == Keys.A && e.Control)
{
 
}
}
 
 
 

 
.................................. C#的事件...........................................
 
事件的理解: 事件是委托类型的对象,比委托多了一个时间性,只能在类内定义跟方法同级;[事件基于委托,为委托提供了一种发布/订阅机制]
把委托上的方法+在事件上,多了 1.在委托的基础上创建事件 2.多了一个触发事件
 
比方说:甲和乙是朋友,上午见面了,乙和甲说,今天中午吃完饭叫我一声,我带你去网吧玩。
在这个情景中,甲吃饭这件事情是乙没有办法控制的。他只可以等甲吃完饭以后发一个信号,
才可以做带甲去网版的这个方法。当然乙可以蹲在甲的家门口一直看着甲,等甲吃完饭然后就可以带他去网吧,但是这样的话,是不是很浪费乙的时间呢
 
委托:是某类方法的签名,
事件:创建事件不会立刻执行,一直监听,等到触发方法发生,才发生[告诉我,什么时候去做]
 
事件的使用:声明委托 --> 声明该委托类型的事件 --> 声明触发事件的方法 --> 为事件+=添加处理方法 --> 调用触发事件的方法
 
步骤:
1. 声明委托:一般命名为 XXXEventHandler ---> publicdelegatevoidXXXEventHandler();
2. 声明事件:一般命名为 XXXEvent ---> public event XXXEventHandler EventName;
3. 声明触发事件的方法:一般命名为 onEventName --->
// 通常为protected和virtual的方法,以允许派生类重写引发事件的逻辑;
// 所以一般会再定义一个方法,调用这个 protected类型的引发事件方法
4. 为事件添加处理方法:用赋值运算符(+=)来为事件附加事件处理方法,(-=)表示删除事件 [用方法名,和委托一样]
5. 调用事件触发方法
 
----------------------------------------------------------------
 
publicclassEventTest
{
publicdelegatevoidNumManipulationEventHandler(); //1. 声明无返回值,无参数的委托
publiceventNumManipulationEventHandler ChangeNumEvent; //2. 声明 NumManipulationEventHandler委托类型的事件
protectedvirtualintOnChangeNum() //3. 声明触发事件的方法
{
if (ChangeNumEvent != null)
{
ChangeNumEvent();
return1;
}
else
{
MessageBox.Show("该事件没有事件处理方法");
return0;
}
}
 
publicvoidSetValue()
{
OnChangeNum();
}
}
publicclassEventTest2
{
publicvoidEventMethod()
{
MessageBox.Show("这是事件的处理方法");
}
}
publicpartialclassForm1 : Form
{
privatevoidbutton1_Click(object sender, EventArgs e)
{
EventTest e1 = newEventTest();
EventTest2 e2 = newEventTest2();
 
//e1.ChangeNumEvent += e2.EventMethod; // 4. 为事件附加处理方法
e1.ChangeNumEvent += newEventTest2().EventMethod; // 4. 用 匿名对象 的方法为事件附加处理方法
// e1.ChangeNum += Event.NumManipulationEventHandler(e2.EventMethod); // C# 1.0的写法
e1.SetValue();
}
}
 
 
----------------------------------------------------------------
 
// 为 button1.Click 事件添加 处理方法 FirstButtonClick( )
// 1. EventHandler 是 C#的委托 2. 创建该委托事件:button1_Click 3.声明触发事件的方法:用户点击 4. 5. 用事件触发程序
 
publicForm1()
{
InitializeComponent();
button1.Click += newEventHandler(FirstButtonClick); // 4. 为事件附加处理方法
}
 
privatevoidFirstButtonClick(object sender, EventArgs e)
{
MessageBox.Show("事件处理方法后");
}
privatevoidbutton1_Click(object sender, EventArgs e) // 2. 声明事件,
{
MessageBox.Show("Button_Click先");
}
 
 
 

 
.................................. 匿名函数:匿名方法 .................................
 
 
匿名方法:
// 参数: 要是没有参数,"()"可以省略
// 返回值:返回值由 return 语句决定,没有 return 语句就没有返回值
delegate(参数)
{
语句块
};
 
作用:创建委托的实例;
将创建委托实例和委托方法结合在一起,省略了委托方法的方法名;
delegatevoidNumberChanger(int n);
NumberChanger nc = delegate(int x) // 委托方法的参数是 int x,没有return,即void
{
Console.WriteLine("Anonymous Method: {0}", x);
};
 
 

 
.................................. Lambda表达式: .................................
 
 
 
Lambda表达式:提供一种匿名方法的新语法,不用写参数类型[更简洁],缺点:不用使用 ref/out参数
// 参数: 0个参数:用"()"; 1个参数:"()" 是可选的;也可以显示执行参数类型,(int x,string y) => {}: 但必须全是隐式或全是显示
// 主体: 只有一条语句,"{ }" 可以省略
// 返回值:1.主体只有一行代码时,"{}"可以省略 2.有 "return"关键字就要有"{}"
(参数) => { 语句块 };
 
作用1:创建委托的实例
任何 Lambda 表达式都可以转换为委托类型。 Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。
如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型;有返回值,可将其转换为 Func 委托类型。
 
delegatevoidTestDelegate(string s);
privatevoidbutton2_Click(object sender, EventArgs e)
{
TestDelegate myDel = n => { string s = n + " " + "World"; MessageBox.Show(s); }; // 将 Lambda表达式 赋值 给 委托的一个实例
myDel("Hello");
}
 
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
 
作用2:在Linq查询中使用
[ .Where( u => u> 0); ]
 
 

 
.................................. Linq查询 .................................
 
 
Linq [Language Integrated Query]: 语言集成查询,允许编写C#代码 与查询SQL相同的方式 来查询内存数据 [数据可能是SQL,XMS,Web]
是C#中对不同来源数据的统一查询方法
 
C#中提供了两个特别醒目的类:Enumerable和Queryable。
两者都在System.Linq命名空间中。在这两个类中,含有许许多多的扩展方法。
Enumerable的大多数扩展的是IEnumerable<T>,Queryable的大多数扩展的是IQueryable<T>。
它们赋予了集合强大的查询能力,共同构成了LINQ的重要基础。
 
Linq的用途:1. 用于同一查询 2.创建匿名类型数组
Linq的使用流程: 创建数据源 ---> 用var变量存储 用[查询语句 / 查询方法] 执行查询的结果 ---> 在foreach中读取var变量中的数据
Linq的两种使用方式: 查询语句, 查询方法
Linq的注意事项:
1. Lambda表达式用于Linq中,1.只有一条语句,既不用加"{}"也不用加";"2.不止一条语句,即要加"{}"也要加";"用于区分
2. Linq执行的顺序就是书写的顺序
3. Linq会延迟执行
Select语句不会触发 Linq的执行;
foreach循环,ToList(),ToArray(),ToDictionary() 触发 Linq执行
*--------------------------------------Begin
var str = newList<string>() { "Ben", "Lily", "Joel", "Sam", "Annie" };
// 1. Select语句不会触发 Linq的执行
var upperstr = str
.Select(s => { Console.WriteLine(s); return s.ToUpper(); })
.Where(s => s.Length > 3);
foreach (var st in upperstr)
{
Console.WriteLine(st);
}
// 这是因为在开始foreach枚举时,upperstr 的成员还没确定,
// 我们在每次foreach枚举时,都先运行select,打印原名,遇到 return ,如果长度大于3,才在foreach中打印
 
// Ben
// Lily
// Joel
// JOEL
// Sam
// Annie
// ANNIE
 
 
// 2. foreach循环,ToList(),ToArray(),ToDictionary() 触发 Linq执行
var upperstr2 = str
.Select(s => { Console.WriteLine(s); return s.ToUpper(); })
.Where(s => s.Length > 3)
.ToList();
 
// ToList方法强制执行了所有LINQ语句。所以upperstr在foreach循环之前就确定了。upperstr 仅仅包含三个成员
// 故将先打印5个名字,再打印uppercase中的三个成员,
 
// Ben
// Lily
// Joel
// Sam
// Annie
// LILY
// JOEL
// ANNIE
*--------------------------------------END
 
 
1.查询语句:按SQL的顺序编写
var str = newList<string> { "Ben", "Lilyly", "Jone", "Annie" };
var str2 = newList<string> { "Ben", "Jone", "Lily" };
 
// join...on... [ 全连接 ] ; Linq 会读取整个右边序列,所以尽量将 小表放在右边
var upperstr = from table1 in str // table1:是表的别名,可以随便取
join table2 in str2
on table1 equals table2
where table1 != "Ben"// where 和 let 可以互换位置
let name1 = table1 // let子句:引入临时变量
let name2 = table2
selectnew { Name_1 = table1, Name_2 = table2 }; // new 关键字创建匿名类型: select new { 属性名=p, 属性名=p.Length }
 
// join...on...into...分组连接 [ 左外连接 ]
var upperstr2 = from table1 in str
join table2 in str2
on table1 equals table2
into table3
select table3;
 
// 多个from字句 [ 笛卡尔积 ]
var upperstr3 = from table1 in str
from table2 in str2
selectnew { Name_1 = table1, Name_2 = table2 };
 
// group...by... 不能和 select 连用
var upperstr4 = from table1 in str
orderby table1
group table1 by table1.Length;
 
foreach(var st in upperstr4)
{
Console.WriteLine(st);
}
 
2.查询方法: 将关键字封装成方法来使用,一般配合 Lambda 表达式来使用
int[] x = {1,2,3,4,5}; //创建数据源
var x1 = x.Where(u => u > 0); // 定义查询表达式 <==> form u in x where u > 0
// var x1 = x.Where(u => u > 0);
 
------------------------------- Begin
 
-- Select [将序列中的每个元素投影到新表中]
var x1 = x.Where(u => u > 0)
.Select(u => u.ToString());
 
-- First [返回序列中满足指定条件的第一个元素]
var query = x.First(u => )
 
------------------------------- End
 
 
 
作者:大李子
邮箱:1185241994@qq.com
本文版权归作者所有,欢迎转载,未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
posted @ 2021-03-17 09:40  大李子"  阅读(222)  评论(0)    收藏  举报