委托与事件
一 委托
1-标准写法
定义一个委托:public delegate void EventHandler(object sender,EventArgs e);
2- 对委托的描述
1)首先委托是一个类(引用类型),因此在定义时最好将定义委托的位置与其他类平行;
2)定义委托需要约定好返回值和参数;声明一个委托类型的变量,指向符合这个委托类型约束格式的函数(回调函数);对指向函数的格式(返回值和参数)进行了约束;
3)委托相当于一个函数容器,可以通过委托变量把需要的多个函数传出。
3-委托实现实例
namespace 委托
{
//定义委托
public delegate bool TestDelegate(string str);
//委托调用类
class Program
{
static void Main(string[] args)
{
MyTest myTest = new MyTest();
//****委托四种写法****
//与回调函数进行绑定,绑定函数需要符合定义委托类型的格式
//myTest.m_testDel = new TestDelegate(Say);
myTest.m_testDel = Say;//上面方式的简写
Func<string, bool> func = Say; //使用泛型委托的写法Func<T>,bool为返回值类型
Predicate<string> p = Say;//返回值bool且一个参数类型,符合Predicate<T>的格式
bool isExcute = myTest.m_testDel("Tom");//委托执行回调函数
if (isExcute)
{
//【多播委托】 委托容器内可以承载多个函数,但是第一次绑定需要实例化不能使用+=
myTest.m_testDel -= Say;//注销函数
myTest.m_testDel += Show;//绑定新函数
myTest.m_testDel("OK");
}
//将委托封装在注册函数内
Program program = new Program();
program.OnExcute(Say, "Tom");
//program.OnExcute(myTest.m_testDel,"Tom");方法注册到委托后,效果同上
Console.ReadLine();
}
//所谓的注册函数就是一个普通函数,目的是为了封装委托
private void OnExcute(TestDelegate testDel,string strText)
{
if (testDel != null)
{
testDel(strText);
}
}
private static bool Say(string strName)
{
Console.WriteLine(String.Format("Hi! {0}", strName));
return true;
}
private static bool Show(string strShow)
{
Console.WriteLine(String.Format("{0} 完成", strShow));
return true;
}
}
//委托封装类
class MyTest
{
//委托类型成员变量
public TestDelegate m_testDel;
//内部封装,对委托进行保护
public void WantSay(string name)
{
if (m_testDel != null)
{
m_testDel(name);
}
}
}
}
4-泛型委托
描述:.Net已经定义了三种类型的泛型委托,分别是 Predicate、Action、Func。在使用linq的方法语法中,我们会经常遇到这些类型的参数。
优势:1)可以减少定义委托形式的重复操作,省去了定义委托类型的步骤。
2)虽然delegate也可以写成泛型,例如public delegate void MyDelegate<T>(Targ),但是泛型委托的优势是传入参数 个数不固定可以灵活增减(参数个数范围0-16),参数的类型动态可变。
3)有了泛型委托,我们就不用到处定义委托类型了,除非不满足需求,否则都应该优先使用内置的泛型委托。
三种类型:
1) Action无返回值,Action重载的参数个数有0-16个
Action<T> 例如: public delegate void Action<T>(Tobject)
2)Func有返回值。Func重载的参数个数有0-16个
Func<T> 例如:public delegate TResult Func<T, out TResult>(T arg);
注意:定义Func类型委托时,最后需要传入返回值的类型。例如:Func<int,int> action =PrintfMyAge; 返回值为int类型
3)Predicate
这是一种特殊的Func,因为只有一种形式且满足Func类型要求。
即public delegatebool Predicate<T>(T obj);有返回值且要求只能为bool类型,有参数且要求只能有一个泛型参数。
用处:用于封装定义了一组条件并确定指定对象是否符合这些条件的方法。
namespace 泛型委托
{
class TestAction
{
//注释的这一行是Action的原型,这句可写可不写
//注释的这一行是Action的原型,由此可看出Action无返回值
public delegate void Action<T>(T arg);
static void Main(string[] args)
{
Action<int> action = PrintfMyAge;
action(18);
Action<string> action2 = PrintfMyName;
action2("Tom");
}
static void PrintfMyAge(int myAge)
{ Console.WriteLine("My Age is {0}", myAge); }
static void PrintfMyName(string myName)
{ Console.WriteLine("My Name is {0}", myName); }
}
class TestFunc
{
//注释的这一行是Func的原型,这句可写可不写
//注释的这一行是Func的原型,由此可看出Func有返回值
public delegate TResult Func<T, out TResult>(T arg);
static void Main(string[] args)
{
Func<int, int> action = PrintfMyAge;
int n1 = action(18);
Func<string, int> action2 = PrintfMyName;
int n2 = action2("Tom");
Console.ReadLine();
}
static int PrintfMyAge(int myAge)
{ Console.WriteLine("My Age is {0}", myAge); return 1; }
static int PrintfMyName(string myName)
{ Console.WriteLine("My Name is {0}", myName); return 1; }
}
//自定义泛型委托
public delegate void MyGenericDelegate<T>(T arg);
class GenericDelegate
{
static void Main(string[] args)
{
MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget);
StringTarget("Some string data");
MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget);
intTarget(9);
Console.ReadLine();
}
static void StringTarget(string arg)
{
Console.WriteLine("arg in uppercase is : {0}", arg.ToUpper());
}
static void IntTarget(int arg)
{
Console.WriteLine("++arg is : {0}", ++arg);
}
}
}
二 事件
1-描述
事件是类型安全的委托,因此事件属于委托的子集,声明一个事件需要先定义一个委托在加上Event修饰符,也可以说它是一种具有事件性质的委托
2-事件的标准写法:
1)首先,声明一个具有事件性质的委托(可以省略,标准事件的委托类型为EventHandler,该委托类型可以不用定义,.Net已经封装好可以直接使用)
事件执行所需要的参数:
public delegate void EventHandler(object sender, EventArgs e);
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
sender数据源,表示触发事件的对象;//例如点击事件的对象是button;
e 是执行这个事件所需要的数据资源;//EventArgs是.Net定义好的事件资源类,可以继承。泛型EventHandler<TEventArgs>可设计自定义资源类。
2)然后,声明事件需要用event进行修饰
事件声明标准写法:事件名+EventHandler 定义一个事件习惯上以EventHandler结尾
声明一个事件 :public event EventHandler myEventHandler;
public event EventHandler<TEventArgs> myEventHandler;
3-委托与事件的区别及联系
委托与事件的区别:委托在任何地方都可以进行调用,事件则需要满足某些条件,且进行+=注册后才会触发,在外部事件接收类内注册,内部事件触发类内调用
委托与事件的联系:1)事件本质上在封装了多播委托(类似于属性封装字段),旨在保护委托,因为委托不安全在任何地方都可以调用,因此委托的本质是一个类型,事件的本质就是一个类型安全的委托。
2)属性是用Get_/Set_方法提供访问私有字段(非委托)的方法,事件就是用Add_/Remove_方法提供访问私有委托字段的方法。
3)委托的本质是引用类型(一个类),用于承载回调函数,委托用于实现回调机制;事件的本质是委托,事件是回调机制的一种应用。
委托与事件总结:
委托的作用:实现占位,在不知道将来要执行的方法的具体代码时,可以先用一个委托变量来代替方法调用(委托的返回值,参数列表要明确)。在委托调用之前,需要先给委托赋值,否则为null();
事件的作用:事件的作用与委托变量一样,只是在功能上比委托变量有更多的限制。(比如1.只能通过+=或-=来绑定方法(事件处理程序)2.只能在事件发送类的内部调用(触发)事件。)
4-事件实现步骤
要创建一个事件驱动的程序需要下面的步骤:
1)声明关于事件的委托;(使用.Net封装的委托类可以省略此步骤)
2)定义事件发送时所需要的附加信息类;(按照约定,所有的事件的附加信息都应该从EventArgs派生,但泛型EventHandler<TEventArgs>可以设计自定义类。)
3)声明事件;
4)编写触发事件的函数;(引发事件的代码常常被编写成一个函数,.NET约定这种函数的名称为“OnEventName”,比如OnAlarm()的函数。在函数内需判断事件变量是否为空)
5)创建事件处理程序;(在外部事件接收类内创建,返回值与入参要与事件委托格式一致)
6)注册事件处理程序;(与相关事件变量进行注册+=或注销-=)
7)在适当的条件下触发事件。
5-事件实现实例
namespace 事件
{
//事件接收类
class Program
{
static void Main(string[] args)
{
Person person = new 事件.Person();
person.age = "18";
person.name = "Tom";
person.tag = "1-1";
MailMgr mail = new MailMgr(person);
//委托注册
mail.m_personDel = (sender, p) =>//Lamda创建符合委托类型格式的函数;定义的参数在执行委托时传入
{
Console.WriteLine(string.Format("fax receive,from:{0} to:{1} content is:{2}", p.name, p.age, p.tag));
};
//委托可以在任何地方调用(与事件的区别)
mail.m_personDel(null, person);
//事件只能在定义事件的事件发送类内进行调用 6)注册事件处理程序;
mail.personEventHandler += (sender, p) =>//5)创建事件处理程序;定义的参数在执行事件时传入
{
Console.WriteLine(string.Format("fax receive,from:{0} to:{1} content is:{2}", p.name, p.age, p.tag));
};
//7)在适当的条件下触发事件。调事件发送类内部的事件触发函数
mail.OnMail();
Console.ReadLine();
}
}
//声明一个委托,与类的位置平行
public delegate void PersonDel(object sender, Person p);
//事件发送类
class MailMgr
{
//3)声明事件;
public event EventHandler<Person> personEventHandler;
public PersonDel m_personDel;
Person m_p = new Person();
public MailMgr(Person p)
{
m_p = p;
}
//4)编写触发事件的函数;
public void OnMail()
{
//不注册事件,无法回调函数
if (personEventHandler != null)
{
//仅在事件发送类内部调用
personEventHandler(this, m_p);
}
}
}
//2)定义事件发送时所需要的附加信息类;
public class Person
{
public string name { set; get; }
public string age { set; get; }
public string tag { set; get; }
}
}