按自己的想法去理解事件和泛型(C#)
上一篇那些年困扰我们的委托(C#)讲了委托,这一篇自然就轮到事件了。
不喜欢官方的表达方式,喜欢按照自己的想法去理解一些抽象的东西,我是一个喜欢简单怕麻烦的人。
事件
考虑到委托使用的一些缺陷,就有了事件。委托是不安全的,打个比方,如果把委托当作共有字段,那么事件就相当于是属性的概念。
事件就是被限制使用的委托变量,事件里面封装了一个多播委托。
事件语法:public event 委托类型 事件名;
事件的作用:事件的作用与委托变量一样,只是功能上比委托变量有更多的限制。比如:只能通过+=或者-=来绑定方法。只能在类内部调用事件。
当一个结果发生时,有可能引起另外的一些反应,这就好像因果关系。而事件则是这个因与果的内部联系。
事件的本质:委托的一个实例,添加了event关键字修饰。
委托是一种类型,事件是委托类型的实例。
和委托的区别:
- 事件不能用=来注册方法。(防止外面直接赋值为null,导致注册失效)
- 事件不能被外部调用(安全性控制)
整个windows系统都是通过事件驱动的,事件都有触发条件。
在WebForm或者WinForm中,我们经常看到:
private void button1_Click(object sender, EventArgs e)
{
//代码
}
上面是一个按钮的单击事件。从上可以看到三个事件因素:
- 对象:button
- 事件名:click
- 参数:object sender,事件源,在这里其实就是button,eventArgs e是事件需要的资源数据。
我们在Winform中都是通过如下的方式来注册事件的。
this.button1.Click += new System.EventHandler(this.button1_Click);
EventHandler就是一个委托:
public delegate void EventHandler(object sender, EventArgs e);
这也就是为什么我们注册的事件总是有sender和e这两个参数,因为委托就是这样声明的。我们来自定义一个事件:
public event EventHandler OnSay;
public Form1()
{
InitializeComponent();
OnSay += Form1_OnSay;
} void Form1_OnSay(object sender, EventArgs e)
{
Console.Write("你好吗");
}
我们通过Reflector工具来查看:
事件OnSay中,其实是2个方法,我们来看下源码:
public void add_OnSay(EventHandler value)
{
EventHandler handler2;
EventHandler onSay = this.OnSay;
do
{
handler2 = onSay;
EventHandler handler3 = (EventHandler)Delegate.Combine(handler2, value);
onSay = Interlocked.CompareExchange<EventHandler>(ref this.OnSay, handler3, handler2);
}
while (onSay != handler2);
}
public void remove_OnSay(EventHandler value)
{
EventHandler handler2;
EventHandler onSay = this.OnSay;
do
{
handler2 = onSay;
EventHandler handler3 = (EventHandler)Delegate.Remove(handler2, value);
onSay = Interlocked.CompareExchange<EventHandler>(ref this.OnSay, handler3, handler2);
}
while (onSay != handler2);
}
这里可以看出对事件的操作,其实最终还是体现在对委托的操作。
泛型
为什么要有泛型?
更好的实现代码复用,但是它不是通过面向对象的思想来实现代码复用。面向对象惯用的三板斧:封装、继承、多态。
我们先来看一下代码,假设在一个类中有多个方法,他们的操作很类似,可能仅仅只是传入的参数类型不同而已
using System; namespace GenericsDemo
{
public class MethodTest
{
public void IntShow(int i)
{
Console.WriteLine(string.Format("IntShow方法,参数类型{0}",i.GetType()));
}
public void StrShow(string s)
{
Console.WriteLine(string.Format("StrShow方法,参数类型{0}", s.GetType()));
}
}
}
如果一个类中存在多个这样的方法,我们总不能把所有的方法都这么写一遍吧,有没有一种方式来将这些方法进行合并呢?
这个时候我们会想到Object是任何类型的父类,任何父类出现的地方,都可以使用子类来代替。接下来,我们来改造一下代码实现:
public void ObjShow(object obj)
{
Console.WriteLine(string.Format("ObjShow方法,参数类型{0}", obj.GetType()));
}
我们来看下调用:
_MethodTest.IntShow();
_MethodTest.StrShow(""); _MethodTest.ObjShow();
_MethodTest.ObjShow("");
方法是合并了,但是现在存在什么样的问题?出现了装箱拆箱,严重影响性能。而且不够安全,因为如果当我把代码进行如下修改时,会发生什么
public void ObjShow(object obj)
{
//Console.WriteLine(string.Format("ObjShow方法,参数类型{0}", obj.GetType()));
Console.WriteLine(string.Format("ObjShow方法,参数类型{0},参数值{1}", obj.GetType(),Convert.ToInt32(obj)));
}
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-font-kerning:1.0pt;}
调用代码:
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-font-kerning:1.0pt;}
_MethodTest.ObjShow("a");
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-font-kerning:1.0pt;}
编译时不会报错,但是运行时就报错了。也就是说通过object来作为参数传递,其实是存在严重的安全隐患的。
那么有没有什么办法来解决这两个问题呢?C#2.0泛型的出现正是基于这样的需求。
public void GenericsShow<T>(T t)
{
Console.WriteLine(string.Format("GenericsShow方法,参数类型{0}", t.GetType()));
}
调用代码:_MethodTest.GenericsShow<int>(1);
这样依赖,泛型方法在申明的时候能够实现类似于Objet的效果,在调用时先确定类型,这样就达到了安全检查的目的。
泛型就像是使用了一个类型占位符,而这一特性在使用集合时更能体现其强大之处。
也正是由于泛型太强大了,强大得像孙悟空一样,我们需要弄一道紧箍咒来对其进行束缚,否则不容易控制。这时,就有了泛型约束,它在泛型方法或者泛型委托声明之时就对其进行限定。限定关键字通过where。
public class Student
{
public string Name { get; set; }
}
public void StudentShow<T>(T t) where T : Student
{
Console.WriteLine(string.Format("GenericsShow方法,参数类型{0}", t.GetType()));
}
public void GenericsShow<T>(T t)
{
Console.WriteLine(string.Format("GenericsShow方法,参数类型{0}", t.GetType()));
}
public void GenericsShow<T>(T t)
{
Console.WriteLine(string.Format("GenericsShow方法2,参数类型{0}", t.GetType()));
}
需要注意的是,这里使用了泛型重载,这个时候编译是可以正常通过的,可是注意了,调用的时候就出现问题了
为什么会这样呢?因为泛型的类型参数在编译器并不能确定其类型,而重载时进行类型检查发送在实例方法被调用时。
同时需要注意的是,当一般方法和泛型方法同时调用时,优先选择一般方法,因为编译器会进行类型推断。
泛型的运用远不止于此,它还支持泛型继承、泛型接口、泛型类、泛型委托等。
Normal
0
7.8 磅
0
2
false
false
false
EN-US
ZH-CN
X-NONE
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:普通表格;
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-parent:"";
mso-padding-alt:0cm 5.4pt 0cm 5.4pt;
mso-para-margin:0cm;
mso-para-margin-bottom:.0001pt;
mso-pagination:widow-orphan;
font-size:10.5pt;
mso-bidi-font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-font-kerning:1.0pt;}
按自己的想法去理解事件和泛型(C#)的更多相关文章
- Spark2.1.0——深入理解事件总线
Spark2.1.0——深入理解事件总线 概览 Spark程序在运行的过程中,Driver端的很多功能都依赖于事件的传递和处理,而事件总线在这中间发挥着至关重要的纽带作用.事件总线通过异步线程,提高了 ...
- 从需求的角度去理解Linux系列:总线、设备和驱动
笔者成为博客专家后整理以前原创的嵌入式Linux系列博文,现推出以让更多的读者受益. <从需求的角度去理解linux系列:总线.设备和驱动>是一篇有关如何学习嵌入式Linux系统的方法论文 ...
- 从逆向的角度去理解C++虚函数表
很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中 ...
- 从有限状态机的角度去理解Knuth-Morris-Pratt Algorithm(又叫KMP算法)
转载请加上:http://www.cnblogs.com/courtier/p/4273193.html 在开始讲这个文章前的唠叨话: 1:首先,在阅读此篇文章之前,你至少要了解过,什么是有限状态机, ...
- JAVA中的数据结构 - 真正的去理解红黑树
一, 红黑树所处数据结构的位置: 在JDK源码中, 有treeMap和JDK8的HashMap都用到了红黑树去存储 红黑树可以看成B树的一种: 从二叉树看,红黑树是一颗相对平衡的二叉树 二叉树--&g ...
- JSP 生命周期 理解JSP底层功能的关键就是去理解它们所遵守的生命周期
JSP 生命周期 理解JSP底层功能的关键就是去理解它们所遵守的生命周期. JSP生命周期就是从创建到销毁的整个过程,类似于servlet生命周期,区别在于JSP生命周期还包括将JSP文件编译成ser ...
- 可以这样去理解group by和聚合函数
写在前面的话:用了好久group by,今天早上一觉醒来,突然感觉group by好陌生,总有个筋别不过来,为什么不能够select * from Table group by id,为什么一定不能是 ...
- 可以这样去理解group by和聚合函数(转)
http://www.cnblogs.com/wuguanglei/p/4229938.html 写在前面的话:用了好久group by,今天早上一觉醒来,突然感觉group by好陌生,总有个筋别不 ...
- 理解事件捕获。在限制范围内拖拽div+吸附+事件捕获
一.实现的效果是在限制范围内拖拽div+吸附+事件捕获. 这里需要理解的是事件捕获,这个事件捕获也是为了兼容div在拖拽过程中,文本不被选中这个问题. 如此良辰美景,拖拽也可以很洒脱哈.先看看图, 二 ...
随机推荐
- Spring之SpringMVC的Controller(源码)分析
说明: 例子就不举了,还是直接进入主题,本文主要是以SpringMVC的Controller接口为入点,来分析SpringMVC中C的具体实现和处理过程. 1.Controller接口 public ...
- UVA-11134-Fabled Rooks (结构排序+贪婪)
这个问题更像八皇后问题,但在位置在大选前必须进行排序,让左侧的优选位置,我没想到这死脑筋! 行,这个问题是不是代码贴. 版权声明:本文博客原创文章.博客,未经同意,不得转载.
- C++中出现的计算机术语4
adaptor(适配器) 一种标准库类型.函数或迭代器,使某种标准库类型.函数或迭代器的行为类似于第二种标准库类型.函数或迭代器.系统提供了三种顺序容器适配器:stack(栈).queue(队列)以及 ...
- 游戏开发常用JS
游戏插件:cocos2d,createjs,webGl(3d),three.js(3d插件) web插件:Bootstrap插件.less尽量写在服务端. chart.js:精巧的js图标绘制工具库
- Linux内核头文件与内核与库的关系
看上一篇文章中对buildroot的介绍,里面的文档第 3.1.1.1 Internal toolchain backend 节内容 C库会去访问Linux kernel headers(*.h)文件 ...
- leetcode[158] Read N Characters Given Read4 II - Call multiple times
想了好一会才看懂题目意思,应该是: 这里指的可以调用更多次,是指对一个文件多次操作,也就是对于一个case进行多次的readn操作.上一题是只进行一次reandn,所以每次返回的是文件的长度或者是n, ...
- Android项目--Json解析
在过去的一段时间里,我希望做一个天气的应用,但是由于老版的天气接口已经不能用了.只能更新到2014年3月4日. 不过有些东西,哪来学习一下,也是可以的. 比如:http://m.weather.com ...
- java 权限 部分截图
下载地址:http://download.csdn.net/detail/heyehuang/5857263
- Java 多线程编程之九:使用 Executors 和 ThreadPoolExecutor 实现的 Java 线程池的例子
线程池用来管理工作线程的数量,它持有一个等待被执行的线程的队列. java.util.concurrent.Executors 提供了 java.util.concurrent.Exe ...
- 解决C# WinForm 中 VSHOST.EXE 程序不关闭的问题
右击“解决方案”--属性-调试栏-启用调试器部分-“启用Visual studio宿主进程”不勾选