事件
了解事件
事件就是拥有者的一个工具,手机(拥有者)拥有震动马达(工具),可以利用马达震动这个事件,通知手机主人(对象2)
概念统一下面图都是一个意思,同一对象
事件的应用
简单例子
一个事件多个事件处理器
using System;
using System.Timers;
namespace EventConsole
{
class Program
{
static void Main(string[] args)
{
Timer timer = new Timer();//事件拥有者
timer.Interval = 1000;
Boy boy = new Boy();//事件响应者
Girl girl = new Girl();//事件响应者
timer.Elapsed += boy.Action; //绑定数据处理器 +=是订阅 -=是取消订阅 事件发生就去通知订阅了的对象
timer.Elapsed += girl.Action; //绑定数据处理器 +=是订阅 -=是取消订阅
timer.Start();
Console.ReadLine();
}
}
class Boy
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Boy");
}
}
class Girl
{
internal void Action(object sender, ElapsedEventArgs e)
{
Console.WriteLine("Girl");
}
}
}
事件模型之间的关系表现
第一种 推荐1星
拥有者和响应者是两个独立对象
using System;
using System.Windows.Forms;
namespace EventConsole1
{
class Program
{
static void Main(string[] args)
{
Form form = new Form();//事件的拥有者
Controller controller = new Controller(form);//事件的响应者,谁拥有事件的处理器谁就是响应者
form.ShowDialog();
}
}
class Controller
{
private Form form;
public Controller(Form form)
{
if (form != null)
{
this.form = form;
this.form.Click += this.FormClicked;
}
}
//这里的第二个参数和第一个简单例子的参数不一样,所以这个事件处理器不能处理第一个简单例子的事件,并不通用
//事件处理器
private void FormClicked(object sender, EventArgs e)
{
this.form.Text = DateTime.UtcNow.ToString();
}
}
}
第二种 推荐2星
事件和处理器都属于同一对象,属于即是拥有着也是响应者
using System;
using System.Windows.Forms;
namespace EventConsole2
{
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();//事件的响应者 也是事件拥有者,因为FormClicked事件是MyForm中的
form.Click += form.FormClicked;
form.ShowDialog();
}
}
class MyForm : Form
{
internal void FormClicked(object sender, EventArgs e)
{
this.Text = DateTime.UtcNow.ToString();
}
}
}
第三种 推荐3星
事件响应者包含了事件的拥有者
using System;
using System.Windows.Forms;
namespace EventConsole3
{
class Program
{
static void Main(string[] args)
{
MyForm form = new MyForm();
form.ShowDialog();
}
}
class MyForm : Form//我是事件响应者
{
private TextBox textBox;
private Button button;
public MyForm()
{
this.textBox = new TextBox();
this.button = new Button();//我是事件拥有者
this.Controls.Add(this.button);
this.Controls.Add(this.textBox);
this.button.Click += this.ButtonClicked;
this.button.Text = "点击我";
this.button.Top = 20;
}
//响应处理器
private void ButtonClicked(object sender, EventArgs e)
{
this.textBox.Text = "我是事件响应者中的Text";
}
}
}
其他关系
public Form1()
{
InitializeComponent();
this.button3.Click += Button_Click;
//this.button3.Click += new EventHandler(this.Button_Click);//另一种写法
}
private void Button_Click(object sender, EventArgs e)
{
if (sender == this.button1) {
textBox1.Text = "1";
}
if (sender == this.button2)
{
textBox1.Text = "2";
}
if (sender == this.button3)
{
textBox1.Text = "3";
}
}
补充
可以从FormApp中看出事件处理器基于委托的
同样WDF也是
事件的声明
完整声明
1.定义服务员,确定点菜服务需要什么参数 即事件响应者确定好事件处理器。
2.首先定义点菜委托,确定参数 事件处理器约束。 即声明事件类型,根据事件处理器确认事件格式。
3.定义顾客 事件拥有者,内部定义好事件。 即 将第二步的事件作为其字段,使其成为事件拥有者
using System;
namespace EventConsole5
{
class Program
{
static void Main(string[] args)
{
Waiter waiter = new Waiter();//事件的响应者
Customer customer = new Customer();//事件的拥有者
customer.Order += waiter.Action;//事件拥有者的事件与事件响应者的事件处理器绑定
customer.Think();//思考选什么菜后,触发了订单事件,服务员自动做出响应
customer.PayTheBill() ;//结果没问题,响应很成功
}
}
//注意Customer和OrderEventArgs都要加public
//声明委托
//委托命名要符合FooEventHandle
//第一个参数是事件的拥有者,第二个参数是EventArgs类的派生类,作为事件的参数
public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
//委托参数命名要符合xxEventArgs
public class OrderEventArgs : EventArgs
{
public String Name { get; set; }
public String Size { get; set; }
}
public class Customer
{
//事件处理器,用于字段的绑定
private OrderEventHandler OrderEventHandler;
//声明委托的字段,OrderEventHandler作为参数约束
public event OrderEventHandler Order
{
add
{
this.OrderEventHandler += value;
}
remove
{
this.OrderEventHandler -= value;
}
}
public double Bill { get; set ;}
public void PayTheBill()
{
Console.WriteLine("付{0}元",this.Bill);
}
//触发事件命名为OnFoo,即事出有因,何以引发,并且访问级别改为protected,不然又可以借刀杀人了????这里是有问题的
public void Think()
{
if (this.OrderEventHandler != null)
{
OrderEventHandler(this, new OrderEventArgs { Name = "披萨饼", Size = "small" });
}
}
}
public class Waiter
{
//事件处理器满足委托的约束,否则无法绑定
public void Action(Customer customer,OrderEventArgs e)
{
Console.WriteLine("填写菜单-{0}",e.Name);
double prize=10;//记住记住,只有成员变量才会被赋初值
switch (e.Size)
{
case "small":
prize = 5;
break;
case "large":
prize = 15;
break;
default:
break;
}
customer.Bill += prize;
}
}
}
简略的声明格式
public class Customer
{
//声明委托的字段,OrderEventHandler作为参数约束
public event OrderEventHandler Order;
public double Bill { get; set ;}
public void PayTheBill()
{
Console.WriteLine("付{0}元",this.Bill);
}
public void Think()
{
if (this.Order != null)
{
Order(this, new OrderEventArgs { Name = "披萨饼", Size = "small" });
}
}
}
主要区别在于Customer类,矛盾点在于,事件只允许右边出现+=或者-=,但是现在这货又是字段(因为上面的this.Order!=null没用报错),可是event定义了这货是事件,究竟是什么呢,出现了矛盾
视频里面反编译说,这这货两者都是。。。。只是委托字段被隐藏起来,而Order事件一人做两份事。
注意(也不知道为什么要注意)
为什么这里事件要取代委托
借刀杀人(冒名顶替)
从完整声明里面我们可以发现,事件处理器字段是private的(反编译中简易声明也是),而且没用get,set方法,那么也就是说,这个对象的事件处理器字段只能自己内中方法调用,我的点菜事件只能我自己操作,别人是无法冒充操作我的点菜事件的,如果别人冒充我去点菜的话,最后虽然菜还是上我这里,可是点的菜不是我的菜啊。
问题来了,为什么不把public改为private呢,你设为private你让这委托如何绑定事件处理器啊,customer.Order直接报错了。
作案方式还原
public class Customer
{
//将事件改为委托只需要去掉event就可以了
public OrderEventHandler Order;
public double Bill { get; set ;}
public void PayTheBill()
{
Console.WriteLine("付{0}元",this.Bill);
}
public void Think()
{
if (this.Order != null)
{
Order(this, new OrderEventArgs { Name = "披萨饼", Size = "small" });
}
}
}
作案
static void Main(string[] args)
{
Waiter waiter = new Waiter();//事件的响应者
Customer customer = new Customer();//事件的拥有者
customer.Order += waiter.Action;//事件拥有者的事件与事件响应者的事件处理器绑定
customer.Think();//思考选什么菜后,触发了订单事件,服务员自动做出响应
Customer badGuy = new Customer();//坏人
badGuy.Order += waiter.Action;
badGuy.Order(customer, new OrderEventArgs { Name = "汉堡包", Size = "large" });//作案
badGuy.Order(customer, new OrderEventArgs { Name = "薯条", Size = "large" });
badGuy.Order(customer, new OrderEventArgs { Name = "可口可乐", Size = "large" });
customer.PayTheBill();//好人给多了钱
}
通用的委托使用
using System;
namespace EventConsole5
{
class Program
{
static void Main(string[] args)
{
Waiter waiter = new Waiter();
Customer customer = new Customer();
customer.Order += waiter.Action;
customer.Think();
customer.PayTheBill();
}
}
//去掉自定义的委托
// public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public class OrderEventArgs : EventArgs
{
public String Name { get; set; }
public String Size { get; set; }
}
public class Customer
{
//修改为通用的委托
public event EventHandler Order;
public double Bill { get; set ;}
public void PayTheBill()
{
Console.WriteLine("付{0}元",this.Bill);
}
public void Think()
{
if (this.Order != null)
{
Order(this, new OrderEventArgs { Name = "披萨饼", Size = "small" });
}
}
}
public class Waiter
{
//事件处理器参数使其满足通用委托的约束
public void Action(object sender, EventArgs e)
{
//类型转换
Customer customer = sender as Customer;
OrderEventArgs orderEventArgs = e as OrderEventArgs;
Console.WriteLine("填写菜单-{0}", orderEventArgs.Name);
double prize=10;
switch (orderEventArgs.Size)
{
case "small":
prize = 5;
break;
case "large":
prize = 15;
break;
default:
break;
}
customer.Bill += prize;
}
}
}
关于OnFoo
触发事件命名为OnFoo,即事出有因,何以引发,并且访问级别改为protected,不然又可以借刀杀人了?(怎么做到的)
所以需要吧think中触发事件部分提取,单独构成一个onFoo
public void Think()
{
this.OnOrder("披萨","small");
}
protected void OnOrder(String name, String size)
{
if (this.Order != null)
{
Order(this, new OrderEventArgs { Name = name, Size = size });
}
}
事件与委托的关系
继承
概念
继承是全面继承父类属性和方法(访问级别不允许的情况下无法调用,但是实例化内存中还是存在的。构造器不会被继承,需要手动继承),然后横向或者纵向扩展。横向增加对类成员在数量进行扩充,让类变得强大或者臃肿,类的数量不会变多,纵向是不增加类成员的数量,但是对类成员版本进行重写。(理不清)。
- 子类实例其实也是父类实例, ( 子类实例 is 父类)==true,父类实例不是子类实例( 父类实例is 子类)==false
- 父类作为变量引用子类实例,会无法调用子类方法,但是其属性数据保留
- 子类的访问级别不能超过父类,道理很简单,你继承人家的东西,自然要接受人家约束,不能超越。
- 子类会继承父类访问级别protected和public的方法的属性和方法。而internal,在不同项目中属性就不会继承,但是相同项目会继承。总之就是会继承访问级别内的东西。
- 弹幕说子类其实是会继承父类的private方法和成员,在实例化子类时,会为这一部分分配内存,但是子类不可以调用父类的private成员和方法。瞎猜一下:可信度很高,因为如果内存中没有这方法属性的话,那么公开的父类方法中如何调用私密父类方法呢实际运行的时候,如何调用呢。
- protected和public同时存在时,是或关系,不是同时关系,满足其一即可访问
using System;
namespace AnimalsAndPeople
{
class Program
{
static void Main(string[] args)
{
People people = new People() { Name = "周杰伦" };
Animals animals = new Animals();
Animals animalsRefPeople = new People() { Name = "许嵩" };
Console.WriteLine(animals is People);
Console.WriteLine(people is Animals);
animals.eat();
people.PlayComputer();
People p = (People)animalsRefPeople;
animalsRefPeople.eat();
p.PlayComputer();
people.UseAnimalsfun();
//继承父类的构造器,操作
People peopleExtendBase = new People("类型对应父类的构造方法");
Console.WriteLine(peopleExtendBase.Height);
Console.WriteLine(peopleExtendBase.Name);
}
}
class Animals
{
public String Name { get; set; }
public Animals()
{
this.Height = "Animals无参构造高度";
}
public Animals(String name)
{
this.Height = "继承父类的有参构造器高度";
this.Name = name;
}
public String Height
{
get
;
set;
}
internal virtual void eat()
{
Console.WriteLine("吃");
}
}
class People : Animals
{
//默认调用父类的构造方法
public People()
{
this.Height = "People高度";
}
//默认调用父类无参构造,若父类没有无参构造就会报错,除非手动继承
//继承父类的构造器操作
//这里两个name要相同,代表name是父类中的参数
public People(String name):base(name)
{
}
public void PlayComputer()
{
Console.WriteLine(this.Name + "玩电脑");
}
public void UseAnimalsfun()
{
Console.WriteLine(this.Height);
Console.WriteLine(base.Height);
}
}
}
重写
-
重写的方法,根据实例类型调用,也就是说只要实例时子类,哪怕引用变量是父类,调用的方法还是子类的方法
-
如果没有virtual和override,这就不是重写,是隐式隐藏(隐式编译器会报黄色错误,显式式加了new),隐藏意思是父类方法被隐藏了。这时候调用方法,会根据变量类型进行调用,如果变量类型是本身,就调用本身方法,如果变量类似是父类或者爷爷类(滑稽)以上的,就调用父类的。
-
除了第一次需要用virtual,后面的override自带了virtual的意思
-
注意父类方法需要可见的
-
属性get set的重写[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bp4eF7G-1617610023390)(…/…/AppData/Roaming/Typora/typora-user-images/image-20210329160519575.png)]
-
多态基于重写机制
代差是指变量的类型和实例的类型不一致。python和js就是这样,变量是没有类型的,使用不存在不一致情况(这么说怎么觉得有点别扭),所以这些是没有多态的