Asp.Net MVC温故而知新【3】- 修改视图和布局页

  1. Asp.Net MVC温故而知新【1】-添加控制器
  2. Asp.Net MVC温故而知新【2】-添加视图
  3. Asp.Net MVC温故而知新【3】- 修改视图和布局页
  4. Asp.Net MVC温故而知新【4】- 控制器传递数据给视图
  5. Asp.Net MVC温故而知新【5】- 添加模型
  6. Asp.Net MVC温故而知新【6】- 创建连接字符串
  7. Asp.Net MVC温故而知新【7】- 通过控制器访问模型的数据
  8. Asp.Net MVC温故而知新【8】- 生成的代码详解
  9. Asp.Net MVC温故而知新【9】- 使用 SQL Server LocalDB
  10. Asp.Net MVC温故而知新【10】- Edit方法和Edit视图详解
  11. Asp.Net MVC温故而知新【11】- 添加查询
  12. Asp.Net MVC温故而知新【12】- 数据迁移之添加字段
  13. Asp.Net MVC温故而知新【13】- 添加验证
  14. Asp.Net MVC温故而知新【14】- Details 和 Delete 方法详解

下一个项目使用Asp.Net MVC技术进行开发,以前也用过,好几年没用了,今天没事便找了个教程,复习了一下,是Asp.Net MVC4框架,觉得浅显易懂,便复制收藏了。

首先,你希望改变页面顶部的“Application name”链接,这段文字在每个页面都有,是公用的。尽管它出现在程序中的每个页面,但实际上它只写在一个地方。在解决方案资源管理器中找到 /Views/Shared 文件夹,打开_Layout.cshtml 文件。这个页面叫做布局页,放在所有页面都能用的共享文件夹中。

图1:_Layout.cshtml文件的位置
Continue reading “Asp.Net MVC温故而知新【3】- 修改视图和布局页”

Asp.Net MVC温故而知新【2】-添加视图

  1. Asp.Net MVC温故而知新【1】-添加控制器
  2. Asp.Net MVC温故而知新【2】-添加视图
  3. Asp.Net MVC温故而知新【3】- 修改视图和布局页
  4. Asp.Net MVC温故而知新【4】- 控制器传递数据给视图
  5. Asp.Net MVC温故而知新【5】- 添加模型
  6. Asp.Net MVC温故而知新【6】- 创建连接字符串
  7. Asp.Net MVC温故而知新【7】- 通过控制器访问模型的数据
  8. Asp.Net MVC温故而知新【8】- 生成的代码详解
  9. Asp.Net MVC温故而知新【9】- 使用 SQL Server LocalDB
  10. Asp.Net MVC温故而知新【10】- Edit方法和Edit视图详解
  11. Asp.Net MVC温故而知新【11】- 添加查询
  12. Asp.Net MVC温故而知新【12】- 数据迁移之添加字段
  13. Asp.Net MVC温故而知新【13】- 添加验证
  14. Asp.Net MVC温故而知新【14】- Details 和 Delete 方法详解

下一个项目使用Asp.Net MVC技术进行开发,以前也用过,好几年没用了,今天便找了个教程,复习了一下,是Asp.Net MVC4框架,觉得浅显易懂,便复制收藏了。

在本节内容中,我们将修改HelloWorldController类,使用视图模板来干净利索的封装生成HTML响应客户端的过程。

我们将使用Razor视图引擎创建一个视图模板文件。Razor-based 视图模板文件以.cshtml结尾,提供一种优雅的方式使用C#创建HTML输出。Razor可以使编写模板文件的字符数和按键数降到最低,并可以快速的、流畅的编码。

目前控制器类中的Index方法通过硬编码返回一个字符串消息。修改Index方法使其返回一个View对象,代码如下:

代码清单1:Index方法 – HelloWorldController.cs

public ActionResult Index()
{
    return View();
}

代码清单1中的Index方法使用一个视图模板生成HTML返回给浏览器,控制器方法(也叫做action methods),像Index方法一样,通常返回一个ActionResult(或继承自ActionResult),而不是像字符串一样的原始类型。

首先,为HelloWorld控制器创建一个视图文件夹。右键Views,单击“添加”>“新建文件夹”:

图1:新建文件夹菜单

Continue reading “Asp.Net MVC温故而知新【2】-添加视图”

Asp.Net MVC温故而知新【1】-添加控制器

  1. Asp.Net MVC温故而知新【1】-添加控制器
  2. Asp.Net MVC温故而知新【2】-添加视图
  3. Asp.Net MVC温故而知新【3】- 修改视图和布局页
  4. Asp.Net MVC温故而知新【4】- 控制器传递数据给视图
  5. Asp.Net MVC温故而知新【5】- 添加模型
  6. Asp.Net MVC温故而知新【6】- 创建连接字符串
  7. Asp.Net MVC温故而知新【7】- 通过控制器访问模型的数据
  8. Asp.Net MVC温故而知新【8】- 生成的代码详解
  9. Asp.Net MVC温故而知新【9】- 使用 SQL Server LocalDB
  10. Asp.Net MVC温故而知新【10】- Edit方法和Edit视图详解
  11. Asp.Net MVC温故而知新【11】- 添加查询
  12. Asp.Net MVC温故而知新【12】- 数据迁移之添加字段
  13. Asp.Net MVC温故而知新【13】- 添加验证
  14. Asp.Net MVC温故而知新【14】- Details 和 Delete 方法详解

下一个项目使用Asp.Net MVC技术进行开发,以前也用过,好几年没用了,今天没事便找了个教程,复习了一下,是Asp.Net MVC4框架,觉得浅显易懂,便复制收藏了。

MVC代表了模型-视图-控制器。MVC是一个架构良好,可测试性和易于维护的应用程序开发模式。基于MVC模式的应用程序包含:

  • 模型:模型类代表了应用程序中的数据,为强制执行业务规则使用了数据逻辑验证。
  • 视图:应用程序中的视图模板文件用来动态生成HTML响应。
  • 控制器:控制器类处理传入的浏览器请求,获取模型数据,然后指定视图模板,将响应返回给浏览器。

我们将在本教程系列涵盖了所有这些概念,并告诉你如何使用它们来构建应用程序。

首先,让我们创建一个控制器类。在“解决方案资源管理器”中,右键“Controllers”文件夹,然后选择“添加”>“支架”:

图1:添加控制器的菜单

Continue reading “Asp.Net MVC温故而知新【1】-添加控制器”

.NET Framework 类库简介

命名空间 说明
System System   命名空间包含基本类和基类,这些类定义常用的值和引用数据类型、事件和事件处理程序、接口、属性和异常处理。
System.Activities System.Activities 命名空间包含在 Window Workflow Foundation 中创建和处理活动所需要的所有类。
System.AddIn System.AddIn 命名空间包含具有以下用途的类型:确定、注册、激活和控制加载项,允许加载项与主机应用程序进行通信。
System.CodeDom System.CodeDom 命名空间包含具有以下功能的类:代表源代码文档的元素,支持生成用被支持的编程语言编写的源代码并进行编译。
System.Collections System.Collections 命名空间包含具有以下功能的类型:定义各种标准的、专门的、通用的集合对象。
System.ComponentModel System.ComponentModel 命名空间包含具有以下功能的类型:实现组件和控件的运行时和设计时行为。子命名空间支持 Managed Extensibility Framework (MEF),提供用于为 ASP.NET 动态数据控件定义元数据的特性类,包含用于定义组件及其用户界面的设计时行为的类型。
System.Configuration System.Configuration 命名空间包含具有以下用途的类型:处理配置数据,如计算机或应用程序配置文件中的数据。子命名空间包含具有以下用途的类型:配置程序集,编写组件的自定义安装程序,支持用于在客户端和服务器应用程序中添加或删除功能的可插入模型。
System.Data System.Data 包含具有以下用途的类:访问和管理多种不同来源的数据。顶层命名空间和许多子命名空间一起形成 ADO.NET 体系结构和 ADO.NET 数据提供程序。例如,提供程序可用于 SQL Server、Oracle、ODBC 和 OleDB。其他子命名空间包含由 ADO.NET 实体数据模型 (EDM) 和 WCF 数据服务使用的类。
System.Deployment System.Deployment 命名空间包含具有以下功能的类型:支持部署 ClickOnce 应用程序。
System.Device.Location System.Device.Location   命名空间使应用程序开发人员可通过使用一个 API 方便地访问计算机的位置。位置信息可能来自多个提供程序,例如 GPS、Wi-Fi 三角测量和移动电话塔三角测量。System.Device.Location   类提供一个 API,用于在一台计算机上封装多个位置提供程序,并支持在这些提供程序之间无缝地区分优先级和转换。  因此,使用此 API 的应用程序开发人员不需要定制应用程序特定的硬件配置。
System.Diagnostics System.Diagnostics 命名空间包含具有以下功能的类型:能让您与系统进程、事件日志和性能计数器之间进行交互。子命名空间包含具有以下功能的类型:与代码分析工具进行交互,支持协定,扩展对应用程序监控和检测的设计时支持,使用 Windows 事件跟踪 (ETW) 跟踪子系统来记录事件数据,在事件日志中进行读取和写入,收集性能数据,以及读取和写入调试符号信息。
System.DirectoryServices System.DirectoryServices 命名空间包含具有以下功能的类型:能让您通过托管代码访问 Active Directory。
System.Drawing System.Drawing 父命名空间包含具有以下功能的类型:支持基本的 GDI+ 图形功能。子命名空间支持高级二维和矢量图形功能、高级成像功能,以及与打印有关的服务和排印服务。另外,子命名空间还包含具有以下功能的类型:扩展设计时用户界面逻辑和绘图。
System.Dynamic System.Dynamic   命名空间提供支持动态语言运行时的类和接口。
System.EnterpriseServices System.EnterpriseServices 命名空间包含具有以下功能的类型:定义 COM+ 服务体系结构,从而为企业应用程序提供基础结构。子命名空间支持补偿资源管理器 (CRM),这是一个 COM+ 服务,允许将非事务性对象包含在 Microsoft 分布式事务协调程序 (DTC) 事务中。子命名空间在下表中有简要介绍,在此参考中有详细记录。
System.Globalization System.Globalization   命名空间包含定义区域性相关信息的类,这些信息包括语言,国家/地区,正在使用的日历,日期、货币和数字的格式模式,以及字符串的排序顺序。这些类对于编写全球化(国际化)应用程序很有用。  而像 StringInfo 和 TextInfo 这样的类更是为我们提供了诸如代理项支持和文本元素处理等高级全球化功能。
System.IdentityModel System.IdentityModel 命名空间包含用于为 .NET 应用程序提供身份验证和授权的类型。
System.IO System.IO 命名空间包含具有以下功能的类型:支持输入和输出,包括以同步或异步方式在流中读取和写入数据、压缩流中的数据、创建和使用独立存储区、将文件映射到应用程序的逻辑地址空间、将多个数据对象存储在一个容器中、使用匿名或命名管道进行通信、实现自定义日志记录,以及处理出入串行端口的数据流。
System.Linq System.Linq 命名空间包含具有以下功能的类型:支持使用语言集成查询 (LINQ) 的查询。这包括具有以下功能的类型:代表查询成为表达式树中的对象。
System.Management System.Management 命名空间包含具有以下功能的类型:能让您访问有关系统、设备和应用程序的管理信息和管理事件(纳入 Windows Management Instrumentation (WMI) 基础结构中)。另外,这些命名空间还包含检测应用程序所需的类型,可使检测应用程序将其管理信息和事件通过 WMI 展示给潜在的客户。
System.Media System.Media   命名空间包含用于播放声音文件和访问系统提供的声音的类。
System.Messaging System.Messaging 命名空间包含具有以下功能的类型:能让您连接、监控和管理网络上的消息队列,以及发送、接收或查看消息。子命名空间包含具有以下用途的类:扩展对消息类的设计时支持。
System.Net System.Net 命名空间包含具有以下功能的类型:提供适用于许多网络协议的简单编程接口,以编程方式访问和更新 System.Net 命名空间的配置设置,定义 Web 资源的缓存策略,撰写和发送电子邮件,代表多用途 Internet 邮件交换 (MIME) 标头,访问网络流量数据和网络地址信息,以及访问对等网络功能。另外,其他子命名空间还能让您以受控方式实现 Windows 套接字 (Winsock) 接口,能让您访问网络流以实现主机之间的安全通信。
System.Numerics 包含补充由 .NET Framework 定义的数值基元(例如 Byte、Double 和 Int32)的数值类型的 System.Numerics 命名空间。
System.Printing System.Printing 命名空间包含具有以下功能的类型:支持打印,允许访问打印系统对象的属性,允许将其属性设置快速复制到另一个相同类型的对象,支持受控 System.PrintTicket 对象和非受控 GDI DEVMODE 结构的相互转换。
System.Reflection System.Reflection 命名空间包含具有以下功能的类型:能让您以受控方式查看加载的类型、方法和字段,能够动态创建和调用类型。子命名空间包含具有以下功能的类型:能让编译器或其他工具发出元数据和 Microsoft 中间语言 (MSIL)。
System.Resources System.Resources 命名空间包含具有以下功能的类型:能让开发人员创建、存储和管理应用程序的区域性特定资源。
System.Runtime System.Runtime 命名空间包含具有以下功能的类型:支持应用程序与公共语言运行时的交互,支持应用程序数据缓存、高级异常处理、应用程序域内的应用程序激活、COM 互操作、分布式应用程序、序列化和反序列化,以及版本控制等功能。另外,其他子命名空间还能让编译器编写人员指定特性来影响公共语言运行时的运行时行为,在一组代码和其他依赖它的代码之间定义可靠性协定,以及实现 Windows Communication Foundation (WCF) 的持久性提供程序。
System.Runtime.InteropServices.CustomMarshalers 提供 .NET Framework 的内部封送处理支持。
System.Security System.Security 命名空间包含具有以下功能的类:代表 .NET Framework 安全性系统和权限。子命名空间提供具有以下功能的类型:控制对安全对象的访问并进行审核,允许进行身份验证,提供加密服务,根据策略控制对操作和资源的访问,以及支持应用程序创建的内容的权限管理。
System.ServiceModel System.ServiceModel 命名空间包含生成 Windows Communication Foundation (WCF) 服务和客户端应用程序所需要的类型。
System.ServiceProcess System.ServiceProcess 命名空间包含具有以下功能的类型:能让您实现、安装和控制 Windows 服务应用程序,扩展对 Windows 服务应用程序的设计时支持。
System.Speech System.Speech 命名空间包含支持语音识别的类型。
System.Text System.Text 命名空间包含用于字符编码和字符串操作的类型。还有一个子命名空间能让您使用正则表达式来处理文本。
System.Threading System.Threading 命名空间包含启用多线程编程的类型。还有一个子命名空间提供可简化并发和异步代码编写工作的类型。
System.Threading.Tasks.Dataflow System.Threading.Tasks.Dataflow   命名空间提供对处理通过为粗粒度数据流和流水线操作任务?消息的基于角色的编程模型。有关更多信息,请参见 数据流(任务并行库)。
System.Timers System.Timers   命名空间提供 Timer 组件,它使您可以在指定的间隔是引发事件。
System.Transactions System.Transactions 命名空间包含具有以下功能的类型:支持具有多个分布式参与者、多个阶段通知和持久登记的事务。还有一个子命名空间包含具有以下功能的类型:描述 System.Transactions 使用的配置选项。
System.Web System.Web 命名空间包含启用浏览器/服务器通信的类型。子命名空间包含具有以下功能的类型:支持 ASP.NET 窗体身份验证、应用程序服务、服务器上的数据缓存、ASP.NET 应用程序配置、动态数据、HTTP 处理程序、JSON 序列化、将 AJAX 功能并入 ASP.NET, ASP.NET 安全性中,以及 Web 服务。
System.Windows System.Windows 命名空间包含在 Windows Presentation Foundation (WPF) 应用程序中使用的类型,包括动画客户端、用户界面控件、数据绑定和类型转换。System.Windows.Forms 及其子命名空间用于开发 Windows 窗体应用程序。
System.Workflow System.Workflow 命名空间包含具有以下用途的类型:开发使用 Windows Workflow Foundation 的应用程序。这些类型为规则和活动提供设计时和运行时支持,以便配置、控制、托管和调试工作流运行时引擎。
System.Xaml System.Xaml 命名空间包含具有以下功能的类型:支持解析和处理可扩展应用程序标记语言 (XAML)。
System.Xml System.Xml 命名空间包含用于处理 XML 的类型。子命名空间支持 XML 文档或流的序列化、XSD 架构、XQuery 1.0 和 XPath 2.0,以及 LINQ to XML(这是一个内存中 XML 编程接口,方便修改 XML 文档)。
Accessibility Accessibility   及其公开的所有成员都属于组件对象模型 (COM) 辅助功能接口的托管包装的一部分。
Microsoft.Activities Microsoft.Activities 命名空间包含支持针对 Windows Workflow Foundation 应用程序的 MSBuild 和调试器扩展的类型。
Microsoft.Build Microsoft.Build 命名空间包含具有以下功能的类型:以编程方式访问和控制 MSBuild 引擎。
Microsoft.CSharp Microsoft.CSharp 命名空间包含具有以下功能的类型:支持生成和编译用 C# 语言编写的源代码,支持动态语言运行时 (DLR) 和 C# 之间进行互操作。
Microsoft.JScript Microsoft.JScript 命名空间包含具有以下功能的类:支持用 JScript 语言生成代码和进行编译。
Microsoft.SqlServer.Server Microsoft.SqlServer.Server   命名空间包含将 Microsoft .NET Framework 公共语言运行时 (CLR) 集成到 Microsoft SQL Server 和 SQL Server 数据库引擎进程执行环境时所要用到的类、接口和枚举。
Microsoft.VisualBasic Microsoft.VisualBasic 命名空间包含具有以下功能的类:支持用 Visual Basic 语言生成代码和进行编译。子命名空间包含具有以下功能的类型:为 Visual Basic 编译器提供服务,支持 Visual Basic 应用程序模型、My 命名空间、lambda 表达式和代码转换。
Microsoft.VisualC Microsoft.VisualC 命名空间包含具有以下功能的类型:支持 Visual C++ 编译器,实现 STL/CLR 库和 STL/CLR 库通用接口。
Microsoft.Win32 Microsoft.Win32 命名空间提供具有以下功能的类型:处理操作系统引发的事件,操纵系统注册表,代表文件和操作系统句柄。
Microsoft.Windows Microsoft.Windows 命名空间包含支持 Windows Presentation Framework (WPF) 应用程序中的主题和预览的类型。
UIAutomationClientsideProviders 包含单个映射客户端自动化提供程序的类型。
XamlGeneratedNamespace 包含不用于从代码中直接使用的编译器生成的类型。

自动切换IE代理设置,通过使用IE自动配置脚本

1. 什么是IE自动配置脚本
IE自动配置脚本起的作用就是,当IE访问网页的时候会根据脚本文件里面界定的内容来访问。比方说,你在脚本文件里面限定了访问某些IP使用某个代理的时候,访问另外一些IP使用另外的代理,这就很方便通过脚本文件来完成。

2. 如何创建、编辑IE自动配置脚本
1)创建:打开WINDOWS自带的记事本,编辑好代码后另存为“.PAC”格式
2)编辑:使用WINDOWS自带的记事本即可。

3. 如何编写IE自动配置脚本
最简单的格式就是包含一个叫FindProxyForURL的JScript函数,IE通过传入两个变量来调用这个函数,一个是用户浏览的地址URL全路经,一个是这个URL中的主机名部分(host)。

这个FindProxyForURL函数有三种可能的字符串返回值,一是”DIRECT”,就是直接连接,不通过代理;二是”PROXY proxyaddr:port”,其中proxyaddr和port分别是代理的地址和代理的端口;三是”SOCKS socksaddr:port”,其中socksaddr和port分别是socks代理的地址和端口,一个自动代理文件可以是多个选择的组合,其中用分号(;)隔开,如:

function FindProxyForURL(url,host)

{

if (host == "www.mydomain.com")

return "DIRECT";

return "PROXY myproxy:80;

PROXY myotherproxy:8080;

DIRECT";

}

把以上代码另存为proxy.pac的文件,如:c:\proxy.pac,然后在IE的菜单里”工具”->”INTEL选项”->”连接”->局域网设置->把使用自动配置脚本选上,然后在地址栏填入file://c:/proxy.pac,注意file后是两斜杠,要使设置生效,需要关闭IE再重新打开.

现在我们介绍下这个proxy.pac脚本文件,脚本的语法是js语法,js的内置函数可以使用,要实现自动配置代理,需要实现FindProxyForURL这个函数,其参数url代表要访问的连接,host代表要访问连接的主机名,该函数有三个返回参数( direct:直接连接,proxy IP:PORT,socket IP:PORT), 返回结果大小写不敏感.

PAC脚本另外一个重要的应用是多台代理服务器并存的情况下,通过pac脚本的控制,可以实现如下目标:

使用户随机选择使用多台代理服务器中的任意一台来达到流量负载均衡的目的;

使管理员通过PAC脚本控制用户使用和不使用某台代理服务器,这样可以空出时间对代理服务器进行维护;

让服务器工作在主备模式,当主服务器宕机时,会自动切换到其它备用服务器而不会中断服务;

根据访问目的地的不同,自动选择最佳代理服务器。

下面我们介绍几个常用的PAC函数,并举便说明:

1. isPlainHostName(host),判断是否为本地主机,例如以http://myservername/的方式访问,则是直接连接,否则使用代理

function FindProxyForURL(url, host)

{

if (isPlainHostName(host))

return "DIRECT";

else

return "PROXY proxy:80";

}

2. dnsDomainIs(host, “”)、localHostOrDomainIs(host, “”),判断所访问主机是否属于某个域和某个域名,例如属于.company.com域的主机名,www.company.com和home.company.com的直接连接,否则使用代理访问。

function FindProxyForURL(url, host)

{

if ((isPlainHostName(host) ||

dnsDomainIs(host, ".company.com")) &&

!localHostOrDomainIs(host, "www.company.com") &&

!localHostOrDomainIs(host, "home.company.com"))

return "DIRECT";

else

return "PROXY proxy:80";

}

3. isResolvable(host),判断被访问主机名能否被解析.例子演示主机名能否被dns服务器解析,如果能直接访问,否则就通过代理访问。

function FindProxyForURL(url, host)

{

if (isResolvable(host))

return "DIRECT";

else

return "PROXY proxy:80";

}

4. isInNet(host, “”, “”),判断IP是否在某个子网内.例子演示访问IP段的主页不使用代理。

function FindProxyForURL(url, host)

{

if (isInNet(host, "10.0.0.0", "255.255.0.0"))

return "DIRECT";

else

return "PROXY proxy:80";

}

5. shExpMatch(host, “”),判断被访问主机名是否符合某一正则表达式.本例演示根据主机域名来改变连接类型,本地主机、*.edu、*.com分别用不同的连接方式。

function FindProxyForURL(url, host)

{

if (isPlainHostName(host))

return "DIRECT";

else if (shExpMatch(host, "*.com"))

return "PROXY comproxy:80";

else if (shExpMatch(host, "*.edu"))

return "PROXY eduproxy:80";

else

return "PROXY proxy:80";

}

6. url.substring(),取URL字符串的子串.本例演示根据不同的协议来选择不同的代理,http、https、ftp、gopher分别使用不同的代理。

function FindProxyForURL(url, host)

{

if (url.substring(0, 5) == "http:") {

return "PROXY proxy:80";

}

else if (url.substring(0, 4) == "ftp:") {

return "PROXY fproxy:80";

}

else if (url.substring(0, 7) == "gopher:") {

return "PROXY gproxy";

}

else if (url.substring(0, 6) == "https:") {

return "PROXY secproxy:8080";

}

else {

return "DIRECT";

}

}

7. dnsResolve(host),解析地址.本例演示判断访问主机是否某个IP,如果是就使用代理,否则直接连接。

function FindProxyForURL(url, host)

{

if (dnsResolve(host) == "166.111.8.237") {

return "PROXY secproxy:8080";

}

else {

return "PROXY proxy:80";

}

}

8. myIpAddress(),返回自己的IP地址.本例演示判断本地IP是否某个IP,如果是就使用代理,否则直接使用连接。

function FindProxyForURL(url, host)

{

if (myIpAddress() == "10.1.1.1") {

return "PROXY proxy:80";

}

else {

return "DIRECT";

}

}

9. dnsDomainLevels(host),返回被访问主机域名级数.本例演示访问主机的域名级数是几级,就是域名有几个点,如果域名中有点,就通过代理访问,否则直接连接。

function FindProxyForURL(url, host)

{

if (dnsDomainLevels(host) > 0) { // if number of dots in host > 0

return "PROXY proxy:80";

}

return "DIRECT";

}

10. weekdayRange(),判断当前日期日否在某一星期段.本例演示当前日期的范围来改变使用代理,如果是GMT时间周三到周六,使用代理连接,否则直接连接。

function FindProxyForURL(url, host)

{

if(weekdayRange("WED", "SAT", "GMT"))

return "PROXY proxy:80";

else

return "DIRECT";

}

11. 最后一个例子是演示随机使用代理,这样可以好好利用代理服务器。

function FindProxyForURL(url,host)

{

return randomProxy();

}

function randomProxy()

{

switch( Math.floor( Math.random() * 5 ) )

{

case 0:

return "PROXY proxy1:80";

break;

case 1:

return "PROXY proxy2:80";

break;

case 2:

return "PROXY proxy3:80";

break;

case 3:

return "PROXY proxy4:80";

break;

case 4:

return "PROXY proxy5:80";

break;

}

}

 

 

asp.net(included mvc) third party

1. Alexander Chemeris’ ISO C9x compliant inttypes.h for Microsoft Visual Studio (http://code.google.com/p/msinttypes/)
2. Android dirname_r (https://android.googlesource.com/platform/bionic/+/android-4.0.3_r1.1/libc/bionic/dirname_r.c)
3. Anti-Grain Geometry version 2.4 (http://antigrain.com/license/index.html#toc0002)
4. ANTLR (http://www.antlr.org/)
Includes:ANTLR version 3.3.1.7705 (http://www.antlr.org/)
Includes:ANTLR version 3.4.1.9004 (http://www.antlr.org/)
5. ASP.NET (http://www.asp.net/)
Includes:SignalR version 1.1.2 (https://github.com/SignalR/SignalR)
Includes:SignalR version 1.1.3 (https://github.com/SignalR/SignalR)
Includes:SignalR version 2.0 (https://github.com/SignalR/SignalR)
Includes:Web Stack Components (MVC) version 4 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (MVC) version 5 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (WebAPI) version 2 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (WebAPI) version 4 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (WebAPI) version 5 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (Web Pages) version 2 (http://aspnetwebstack.codeplex.com/)
Includes:Web Stack Components (Web Pages) version 3 (http://aspnetwebstack.codeplex.com/)
6. Attractive Chaos’ h.h (https://github.com/attractivechaos/klib/blob/master/khash.h)
7. BERKELEY YACC (http://dickey.his.com/byacc/byacc.html)
8. Bootstrap (http://nuget.org/packages/Twitter.Bootstrap)
Includes:Bootstrap version 2.3.1 (http://nuget.org/packages/Twitter.Bootstrap)
Includes:Bootstrap version 3.0 (http://nuget.org/packages/Twitter.Bootstrap)
9. BSD fnmatch (http://opensource.apple.com/source/gcc/gcc-5666.3/libiberty/bsearch.c)
10. BSD glibc bsearch (http://www.gnu.org/software/libc/download.html)
11. ch-siphash (https://github.com/tanglebones/ch-siphash)
12. Chris Swenson’s sorting routine implementations (https://github.com/swenson/sort)
13. clar (https://github.com/vmg/clar)
14. CommonServiceLocator 1.0 (http://commonservicelocator.codeplex.com/)
15. D3.js version 3.0.6 (http://d3js.org/)
16. DataJS (http://datajs.codeplex.com/)
Includes:DataJS version 1.1.0 (http://datajs.codeplex.com/)
Includes:DataJS version 1.1.1 (http://datajs.codeplex.com/)
17. DotNetOpenAuth (http://www.dotnetopenauth.net/)
Includes:DotNetOpenAuth version 4 (http://www.dotnetopenauth.net/)
Includes:DotNetOpenAuth version 4.1.3 (http://www.dotnetopenauth.net/)
Includes:DotNetOpenAuth version 4.1.4 (http://www.dotnetopenauth.net/)
Includes:DotNetOpenAuth version 4.1.4.12333 (http://www.dotnetopenauth.net/)
18. EnterpriseLibrary (http://entlib.codeplex.com/)
Includes:EnterpriseLibrary.Common version 5.0.505.0 (http://entlib.codeplex.com/)
Includes:EnterpriseLibrary.Logging version 5.0.505.1 (http://entlib.codeplex.com/)
19. Entity Framework (http://entityframework.codeplex.com/)
Includes:Entity Framework version 5 (http://entityframework.codeplex.com/)
Includes:Entity Framework version 6 (http://entityframework.codeplex.com/)
20. Esprima.js (http://esprima.org/)
21. FaceBook C# SDK (http://facebooksdk.net/)
Includes:FaceBook C# SDK version 6.1.4 (http://facebooksdk.net/)
Includes:FaceBook C# SDK version 6.4.2 (http://facebooksdk.net/)
Includes:Simple-Json (https://github.com/facebook-csharp-sdk/simple-json)
Includes:Json.cs (http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html)
22. FDLIBM version 5.3 (http://www.netlib.org/fdlibm/)
23. Freetype 2 Font Engine (http://www.freetype.org/)
24. Git Logo by Jason Long (http://git-scm.com/downloads/logos and http://twitter.com/jasonlong)
25. HighCharts.js (http://www.highcharts.com/)
Includes:HighCharts.js version 2.2.4 (http://www.highcharts.com/)
Includes:HighCharts.js version 3.0.0 (http://www.highcharts.com/)
Includes:php.js (http://phpjs.org)
26. Independent JPEG Group’s JPEG Library (http://www.ijg.org/)
27. JasPer Project version 2.0 (http://www.ece.uvic.ca/~frodo/jasper)
28. Javascript Cookie Library with jQuery bindings and JSON support version 2.2.0 (http://code.google.com/p/cookies/)
29. JGit test (https://github.com/spearce/JGit/commit/e4bf8f6957bbb29362575d641d1e77a02d906739)
30. JGit xhistogram (http://www.eclipse.org/org/documents/edl-v10.php)
31. jqGrid version 4.4.4 (http://www.trirand.net.)
32. jQuery (http://jquery.com/)
Includes:jQuery version 1.7.1 (http://jquery.com/)
Includes:jQuery version 1.8.2 (http://jquery.com/)
Includes:jQuery version 1.9.1 (http://jquery.com/)
Includes:jQuery version 1.10.2 (http://jquery.com/)
Includes:jQuery Core version 1.6.2 (http://jquery.com/)
Includes:jQuery Core version 1.6.4 (http://jquery.com/)
Includes:jQuery Core version 1.7.2 (http://jquery.com/)
Includes:jQuery Core version 1.7.3 (http://jquery.com/)
Includes:jQuery Core version 1.8.2 (http://jquery.com/)
Includes:Sizzle.js (http://sizzlejs.com)
33. jQuery.LazyLoad version 1.8.5 (http://www.appelsiini.net/projects/lazyload)
34. jQuery Mobile (http://jquerymobile.com/)
Includes:jQuery Mobile version 1.1.0 (http://jquerymobile.com/)
Includes:jQuery Mobile version 1.2.0 (http://jquerymobile.com/)
Includes:JQuery Mobile version 1.3.0 (http://jquerymobile.com/)
Includes:JQuery Mobile version 1.3.1 (http://jquerymobile.com/)
35. jQuery postMessage version 0.5 (http://benalman.com/projects/jquery-postmessage-plugin/)
36. jQuery UI (http://jqueryui.com/)
Includes:jQuery UI version 1.8.11 (http://jqueryui.com/)
Includes:jQuery UI version 1.8.20 (http://jqueryui.com/)
Includes:jQuery UI version 1.8.24 (http://jqueryui.com/)
Includes:jQuery UI version 1.9.2 (http://jqueryui.com/)
Includes:jQueryUI version 1.10.0 (http://jqueryui.com/)
Includes:jQueryUI version 1.10.3 (http://jqueryui.com/)
37. jQuery Validation (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
Includes:jQuery Validation version 1.8.1 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
Includes:jQuery Validation version 1.9 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
Includes:jQuery Validation version 1.9.1 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
Includes:jQuery Validation version 1.10 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
Includes:jQuery Validation version 1.11.1 (http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
38. jquery-base64 version 1.0 (https://github.com/yckart/jquery.base64.js)
39. Json2.js (https://github.com/douglascrockford/JSON-js)
Includes:Json2.js version 1.0 (https://github.com/douglascrockford/JSON-js)
Includes:Json2.js version 1.0.2 (https://github.com/douglascrockford/JSON-js)
40. Json.NET version 4.5.6 (http://json.codeplex.com/)
Includes:Json.NET version 4.5.11 (http://json.codeplex.com/)
Includes:Json.NET version 5.0 (http://json.codeplex.com/)
Includes:Json.NET version 5.0.6 (http://json.codeplex.com/)
41. Katana version 2.0 (http://katanaproject.codeplex.com/)
42. Knockout.js (http://knockoutjs.com/)
Includes:Knockout.js version 2.1.0 (http://knockoutjs.com/)
Includes:Knockout.js version 2.2.0 (http://knockoutjs.com/)
Includes:Knockout.js version 2.2.1 (http://knockoutjs.com/)
Includes:Knockout.js version 2.3.0 (http://knockoutjs.com/)
43. Knockout Validation version 1.0.1 (https://github.com/ericmbarnard/Knockout-Validation)
44. LibGit2Sharp (https://github.com/libgit2/libgit2sharp/)
45. LIBXML2 (http://www.xmlsoft.org/)
46. Little CMS Color Management (http://www.littlecms.com/)
47. Log4Net version 2.0.0 (http://logging.apache.org/log4net/)
48. Modernizr (http://www.modernizr.com)
Includes:Modernizr version 2.5.3 (http://www.modernizr.com)
Includes:Modernizr version 2.6.2 (http://www.modernizr.com)
49. Nlog version 2.0.0.2000 (http://nlog-project.org/)
50. NuGet (http://nuget.codeplex.com/)
Includes:NuGet version 2.0 (http://nuget.codeplex.com/)
Includes:NuGet version 2.7 (http://nuget.codeplex.com/)
Includes:NuGet.Core version 1.6.2 (http://nuget.codeplex.com/)
51. OWIN (http://owin.org/)
Includes:OWIN version 1.0 (http://owin.org/)
Includes:OWIN version 2.0 (http://owin.org/)
52. Respond.js version 1.2 (https://github.com/scottjehl/Respond)
53. StructureMap (https://github.com/structuremap/structuremap)
Includes:StructureMap version 2.6.4.1 (https://github.com/structuremap/structuremap)
Includes:StructuremapMap.MVC version 2.6.4.1 (https://github.com/structuremap/structuremap)
54. WebActivator version 1.5.3 (http://nuget.org/packages/WebActivator)
55. WebGrease (http://webgrease.codeplex.com/)
Includes:WebGrease version 1.0 (http://webgrease.codeplex.com/)
Includes:WebGrease version 1.3 (http://webgrease.codeplex.com/)
Includes:WebGrease version 1.5.2 (http://webgrease.codeplex.com/)
56. Volkan Yazc libpqueue (https://github.com/vy/libpqueue)
57. Zlib Decompressor (http://zlib.net/zlib_license.html)

事件和委托

事件和委托

事件是对象发送的消息,以发信号通知操作的发生。操作可能是由用户交互(例如鼠标单击)引起的,也可能是由某些其他的程序逻辑触发的。引发事件的对象称为事件发送方。捕获事件并对其作出响应的对象叫做事件接收方。

在事件通信中,事件发送方类不知道哪个对象或方法将接收到(处理)它引发的事件。所需要的是在源和接收方之间存在一个媒介(或类似指针的机制)。.NET Framework 定义了一个特殊的类型 (Delegate),该类型提供函数指针的功能。

委托是可保存对方法的引用的类。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。这样,委托就等效于一个类型安全函数指针或一个回调。虽然委托具有许多其他的用途,但这里只讨论委托的事件处理功能。一个委托声明足以定义一个委托类。声明提供委托的签名,公共语言运行库提供实现。下面的示例显示了事件委托声明。

public class WakeMeUp 
{
    // AlarmRang has the same signature as AlarmEventHandler.
    public void AlarmRang(object sender, AlarmEventArgs e)
    {...};
    ...
}

只有当事件生成事件数据时才需要自定义事件委托。许多事件,包括一些用户界面事件(例如鼠标单击)在内,都不生成事件数据。在这种情况下,类库中为无数据事件提供的事件委托 System.EventHandler 便足够了。其声明如下。

delegate void EventHandler(object sender, EventArgs e);

事件委托是多路广播的,这意味着它们可以对多个事件处理方法进行引用。有关详细信息,请参见 Delegate。委托考虑了事件处理中的灵活性和精确控制。通过维护事件的已注册事件处理程序列表,委托为引发事件的类担当事件发送器的角色。

如何:将事件处理程序方法连接到事件

若要使用在另一个类中定义的事件,必须定义和注册一个事件处理程序。事件处理程序必须具有与为事件声明的委托相同的方法签名。通过向事件添加事件处理程序可注册该处理程序。向事件添加事件处理程序后,每当该类引发该事件时都会调用该方法。

有关阐释引发和处理事件的完整示例,请参见如何:引发和使用事件。

为事件添加事件处理程序方法

  1. 定义一个具有与事件委托相同的签名的事件处理程序方法。

    public class WakeMeUp 
    {
        // AlarmRang has the same signature as AlarmEventHandler.
        public void AlarmRang(object sender, AlarmEventArgs e)
        {...};
        ...
    }
  2. 使用对该事件处理程序方法的一个引用创建委托的一个实例。调用该委托实例时,该实例会接着调用该事件处理程序方法。
    // Create an instance of WakeMeUp.
    WakeMeUp w = new WakeMeUp();
    
    // Instantiate the event delegate.
    AlarmEventHandler alhandler = new AlarmEventHandler(w.AlarmRang);
  3. 将该委托实例添加到事件。引发该事件时,就会调用该委托实例及其关联的事件处理程序方法。
    // Instantiate the event source.
    AlarmClock clock = new AlarmClock();
    
    // Add the delegate instance to the event.
    clock.Alarm += alhandler;

使用事件

要在应用程序中使用事件,您必须提供一个事件处理程序(事件处理方法),该处理程序执行程序逻辑以响应事件并向事件源注册事件处理程序。我们将该过程叫做事件连结。Windows 窗体和 Web 窗体的可视设计器所提供的应用程序快速开发 (RAD) 工具简化(或者说隐藏)了事件连结的详细信息。

本主题介绍处理事件的常规模式。有关 .NET Framework 中事件模型的概述,请参见事件和委托。有关 Windows 窗体中事件模型的更多信息,请参见如何:在 Windows 窗体应用程序中使用事件。有关 Web 窗体中事件模型的更多信息,请参见如何:在 Web 窗体应用程序中使用事件。

事件模式

由于不同 RAD 工具提供不同级别的支持,所以 Windows 窗体和 Web 窗体中的事件连接详细信息是不同的。不过,两个方案遵循同一事件模式,该模式具有下列特征:

  • 引发名为 EventName 事件的类具有以下成员:
    public event EventNameEventHandler EventName;
  • EventName 事件的事件委托是 EventNameEventHandler,它具有以下签名:
    public delegate void EventNameEventHandler(object sender, EventNameEventArgs e);

 

要使用 EventName 事件,您的事件处理程序必须与事件委托具有相同签名:

void EventHandler(object sender, EventNameEventArgs e) {}

如果事件没有任何关联数据,则引发事件的类使用 System.EventHandler 作为委托,并将 System.EventArgs 作为事件数据。具有关联数据的事件使用从事件数据类型的 EventArgs 派生的类以及相应的事件委托类型。例如,如果您要处理 Windows 窗体应用程序中的 MouseUp 事件,则事件数据类是 MouseEventArgs,而事件委托是 MouseEventHandler。请注意,某些鼠标事件使用事件数据的公共类和公共事件委托,因此命名方案与上面所述的约定不完全匹配。对于鼠标事件,事件处理程序必须具有以下签名:

void Mouse_Moved(object sender, MouseEventArgs e){}

发送方和事件变量参数向事件处理程序提供有关鼠标事件的详细信息。发送方对象指示引发事件的对象。MouseEventArgs 参数提供有关引发事件的鼠标移动的详细信息。许多事件源提供有关事件的其他数据,且许多事件处理程序在处理事件时使用事件特定的数据。有关阐释如何引发和处理具有事件特定数据的事件的示例,请参见如何:引发和使用事件。

静态事件和动态事件

.NET Framework 允许订户为获得事件通知而进行静态或动态注册。静态事件处理程序在其所处理的事件所属类的整个生存期内有效。这是处理事件的最常用方法。动态事件处理程序在程序执行期间显式激活和停用,通常是为了响应某些条件程序逻辑。例如,如果只在特定条件下才需要事件通知,或者如果应用程序提供了多个事件处理程序,并由运行时条件来确定要使用哪个事件处理程序,则可以使用动态事件处理程序。

EventInfo.AddEventHandler 方法添加动态事件处理程序,而 EventInfo.RemoveEventHandler 方法停用这些事件处理程序。各种语言还提供各自的用于动态处理事件的功能。下面的示例定义一个TemperatureMonitor 类,每当温度达到预定义的阈值时该类将引发 TemperatureTheshold 事件。随后,在程序执行期间将激活和停用订阅此事件的事件处理程序。

using System;

public class TemperatureEventArgs : EventArgs
{
   private decimal oldTemp;
   private decimal newTemp;

   public decimal OldTemperature
   {
      get { return this.oldTemp; }
   }

   public decimal NewTemperature
   {
      get { return this.newTemp; }
   }

   public TemperatureEventArgs(decimal oldTemp, decimal newTemp)
   {
      this.oldTemp = oldTemp;
      this.newTemp = newTemp;   
   }
}

public delegate void TemperatureEventHandler(object sender, TemperatureEventArgs ev);

public class TemperatureMonitor
{
   private decimal currentTemperature;
   private decimal threshholdTemperature;

   public event TemperatureEventHandler TemperatureThreshold; 

   public TemperatureMonitor(decimal threshhold)
   {
      this.threshholdTemperature = threshhold;
   }

   public void SetTemperature(decimal newTemperature)
   {
      if ( (this.currentTemperature > this.threshholdTemperature && 
           newTemperature <= this.threshholdTemperature) ||
           (this.currentTemperature < this.threshholdTemperature &&
           newTemperature >= this.threshholdTemperature) )
         OnRaiseTemperatureEvent(newTemperature);
      this.currentTemperature = newTemperature;
   }

   public decimal GetTemperature()
   {
      return this.currentTemperature;
   }

   protected virtual void OnRaiseTemperatureEvent(decimal newTemperature)
   {
      // Raise the event if it has subscribers.
      if (TemperatureThreshold != null)
         TemperatureThreshold(this, new TemperatureEventArgs(this.currentTemperature, 
                               newTemperature));
   }
}

public class Example
{
   public static void Main()
   {
      Example ex = new Example();
      ex.MonitorTemperatures();
   }

   public void MonitorTemperatures()
   {
      TemperatureMonitor tempMon = new TemperatureMonitor(32);
      tempMon.SetTemperature(33);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.", 
                        tempMon.GetTemperature());
      tempMon.SetTemperature(32);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.",
                        tempMon.GetTemperature());

      // Add event handler dynamically using C# syntax.
      tempMon.TemperatureThreshold += this.TempMonitor;

      tempMon.SetTemperature(33);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.", 
                        tempMon.GetTemperature());
      tempMon.SetTemperature(34);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.", 
                        tempMon.GetTemperature());
      tempMon.SetTemperature(32);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.",
                        tempMon.GetTemperature());

      // Remove event handler dynamically using C# syntax.
      tempMon.TemperatureThreshold -= this.TempMonitor;

      tempMon.SetTemperature(31);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.", 
                        tempMon.GetTemperature());
      tempMon.SetTemperature(35);
      Console.WriteLine("Current temperature is {0} degrees Fahrenheit.", 
                        tempMon.GetTemperature());
   }

   private void TempMonitor(object sender, TemperatureEventArgs e)
   {
      Console.WriteLine("   ***Warning: Temperature is changing from {0} to {1}.", 
                        e.OldTemperature, e.NewTemperature);
   } 
}
// The example displays the following output:
//       Current temperature is 33 degrees Fahrenheit.
//       Current temperature is 32 degrees Fahrenheit.
//       Current temperature is 33 degrees Fahrenheit.
//       Current temperature is 34 degrees Fahrenheit.
//          ***Warning: Temperature is changing from 34 to 32.
//       Current temperature is 32 degrees Fahrenheit.
//       Current temperature is 31 degrees Fahrenheit.
//       Current temperature is 35 degrees Fahrenheit.

引发事件

事件功能是由三个互相联系的元素提供的:提供事件数据的类、事件委托和引发事件的类。.NET Framework 具有命名与事件相关的类和方法的约定。如果希望您的类引发一个名为 EventName 的事件,您需要以下元素:

  • 包含事件数据的类,名为 EventNameEventArgs。此类必须是从 System.EventArgs 派生的。
  • 事件的委托,名为 EventNameEventHandler。
  • 引发事件的类。该类必须提供事件声明 (EventName) 和引发事件 (OnEventName) 的方法。

.NET Framework 类库或第三方类库中可能已经定义了事件数据类和事件委托类。在这种情况下,您就不需要定义这些类了。例如,如果您的事件不使用自定义数据,您可以使用 System.EventArgs 作为事件数据并使用System.EventHandler 作为委托。

使用 event 关键字在类中定义事件成员。当编译器在类中遇到 event 关键字时,它会创建一个私有成员,例如:

private EventNameHandler eh = null;

编译器还会创建两个公共方法,即 add_EventName 和 remove_EventName。这些方法是事件挂钩,它们允许委托与事件委托 eh 合并或从该事件委托中移除。这些详细信息对程序员是隐藏的。

定义事件实现后,您必须确定引发事件的时间。通过在定义事件的类或派生类中调用受保护的 OnEventName 方法来引发事件。OnEventName 方法通过调用委托,传入所有事件特定的数据来引发事件。事件的委托方法可以执行事件操作或处理事件特定的数据。

受保护的 OnEventName 方法也允许派生类重写事件,而不必向其附加委托。派生类必须始终调用基类的 OnEventName 方法以确保注册的委托接收到事件。

如果希望处理另一个类中引发的事件,请向事件中添加委托方法。如果您不熟悉 .NET Framework 中事件的委托模型,请参见事件和委托。

如何:在类中实现事件

下面的过程说明如何在类中实现事件。第一个过程实现没有关联数据的事件,它将 System.EventArgs 类和 System.EventHandler 类用作事件数据和委托处理程序。第二个过程实现具有自定义数据的事件,它为事件数据和事件委托处理程序定义自定义类。

有关阐释引发和处理事件的完整示例,请参见如何:引发和使用事件。

实现不具有事件特定的数据的事件

  1. 在类中定义公共事件成员。将事件成员的类型设置为 System.EventHandler 委托。
    public class Countdown 
    {
        ...
        public event EventHandler CountdownCompleted;   
    }
  2. 在引发事件的类中提供一个受保护的方法。对 OnEventName 方法进行命名。在该方法中引发该事件。
    public class Countdown 
    {
        ...
        public event EventHandler CountdownCompleted;   
        protected virtual void OnCountdownCompleted(EventArgs e)    {        if (CountdownCompleted != null)            CountdownCompleted(this, e);    }
    }
  3. 在类中确定引发该事件的时间。调用 OnEventName 以引发该事件。
    public class Countdown 
    {
        ...
        public void Decrement
        {
            internalCounter = internalCounter - 1;
            if (internalCounter == 0)
                OnCountdownCompleted(new EventArgs());
        }
    }

实现具有事件特定的数据的事件

  1. 定义一个提供事件数据的类。对类 EventNameArgs 进行命名,从 System.EventArgs 派生该类,然后添加所有事件特定的成员。
    public class AlarmEventArgs : EventArgs 
    {
       private readonly int nrings = 0;
       private readonly bool snoozePressed = false;
    
       //Constructor.
       public AlarmEventArgs(bool snoozePressed, int nrings) 
       {
          this.snoozePressed = snoozePressed;
          this.nrings = nrings;
       }
    
       //Properties.
       public string AlarmText {  
          ...
       }
       public int NumRings {
          ...
       }
       public bool SnoozePressed{
          ...
       }
    }
  2. 声明事件的委托。对委托 EventNameEventHandler 进行命名。
    public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);
  3. 在类中定义名为 EventName 的公共事件成员。将事件成员的类型设置为事件委托类型。
    public class AlarmClock 
    {
        ...
        public event AlarmEventHandler Alarm;
    }
  4. 在引发事件的类中定义一个受保护的方法。对 OnEventName 方法进行命名。在该方法中引发该事件。
    public class AlarmClock 
    {
        ...
        public event AlarmHandler Alarm;
        protected virtual void OnAlarm(AlarmEventArgs e)    {      if (Alarm != null)           Alarm(this, e);     }
    }
  5. 在类中确定引发该事件的时间。调用 OnEventName 以引发该事件并使用 EventNameEventArgs 传入事件特定的数据。
    Public Class AlarmClock
    {
        ...
        public void Start()
        {
            ...
            System.Threading.Thread.Sleep(300);
            AlarmEventArgs e = new AlarmEventArgs(false, 0);
            OnAlarm(e);
        }
    }

如何:引发和使用事件

下面的示例程序阐释如何在一个类中引发一个事件,然后在另一个类中处理该事件。 AlarmClock 类定义公共事件 Alarm,并提供引发该事件的方法。 AlarmEventArgs 类派生自 EventArgs,并定义 Alarm 事件特定的数据。 WakeMeUp 类定义处理 Alarm 事件的 AlarmRang 方法。 AlarmDriver 类一起使用类,将使用 WakeMeUp 的 AlarmRang 方法设置为处理 AlarmClock 的 Alarm 事件。

该示例程序使用事件和委托和引发事件中详细说明的概念。

// EventSample.cs.
//
namespace EventSample
{  
   using System;
   using System.ComponentModel;

   // Class that contains the data for 
   // the alarm event. Derives from System.EventArgs.
   //
   public class AlarmEventArgs : EventArgs 
   {  
      private readonly bool snoozePressed ;
      private readonly int nrings;

      //Constructor.
      //
      public AlarmEventArgs(bool snoozePressed, int nrings) 
      {
         this.snoozePressed = snoozePressed;
         this.nrings = nrings;
      }

      // The NumRings property returns the number of rings
      // that the alarm clock has sounded when the alarm event 
      // is generated.
      //
      public int NumRings
      {     
         get { return nrings;}      
      }

      // The SnoozePressed property indicates whether the snooze
      // button is pressed on the alarm when the alarm event is generated.
      //
      public bool SnoozePressed 
      {
         get {return snoozePressed;}
      }

      // The AlarmText property that contains the wake-up message.
      //
      public string AlarmText 
      {
         get 
         {
            if (snoozePressed)
            {
               return ("Wake Up!!! Snooze time is over.");
            }
            else 
            {
               return ("Wake Up!");
            }
         }
      }  
   }

   // Delegate declaration.
   //
   public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

   // The Alarm class that raises the alarm event.
   //
   public class AlarmClock 
   {  
      private bool snoozePressed = false;
      private int nrings = 0;
      private bool stop = false;

      // The Stop property indicates whether the 
      // alarm should be turned off.
      //
      public bool Stop 
      {
         get {return stop;}
         set {stop = value;}
      }

      // The SnoozePressed property indicates whether the snooze
      // button is pressed on the alarm when the alarm event is generated.
      //
      public bool SnoozePressed
      {
         get {return snoozePressed;}
         set {snoozePressed = value;}
      }
      // The event member that is of type AlarmEventHandler.
      //
      public event AlarmEventHandler Alarm;

      // The protected OnAlarm method raises the event by invoking 
      // the delegates. The sender is always this, the current instance 
      // of the class.
      //
      protected virtual void OnAlarm(AlarmEventArgs e)
      {
        AlarmEventHandler handler = Alarm; 
        if (handler != null) 
        { 
           // Invokes the delegates. 
           handler(this, e); 
        }
      }

      // This alarm clock does not have
      // a user interface. 
      // To simulate the alarm mechanism it has a loop
      // that raises the alarm event at every iteration
      // with a time delay of 300 milliseconds,
      // if snooze is not pressed. If snooze is pressed,
      // the time delay is 1000 milliseconds.
      //
      public void Start()
      {
         for (;;)    
         {
            nrings++;      
            if (stop)
            {
               break;
            }

            else if (snoozePressed)
            {
               System.Threading.Thread.Sleep(1000);
               {
                  AlarmEventArgs e = new AlarmEventArgs(snoozePressed, 
                     nrings);
                  OnAlarm(e);
               }
            }
            else
            {
               System.Threading.Thread.Sleep(300);
               AlarmEventArgs e = new AlarmEventArgs(snoozePressed, 
                  nrings);
               OnAlarm(e);
            }           
         }
      }
   }

   // The WakeMeUp class has a method AlarmRang that handles the
   // alarm event.
   //
   public class WakeMeUp
   {

      public void AlarmRang(object sender, AlarmEventArgs e)
      {

         Console.WriteLine(e.AlarmText +"\n");

         if (!(e.SnoozePressed))
         {
            if (e.NumRings % 10 == 0)
            {
               Console.WriteLine(" Let alarm ring? Enter Y");
               Console.WriteLine(" Press Snooze? Enter N"); 
               Console.WriteLine(" Stop Alarm? Enter Q");
               String input = Console.ReadLine();

               if (input.Equals("Y") ||input.Equals("y")) return;

               else if (input.Equals("N") || input.Equals("n"))
               {
                  ((AlarmClock)sender).SnoozePressed = true;
                  return;
               }
               else
               {
                  ((AlarmClock)sender).Stop = true;
                  return;
               }
            }
         }
         else
         {
            Console.WriteLine(" Let alarm ring? Enter Y"); 
            Console.WriteLine(" Stop Alarm? Enter Q");
            String input = Console.ReadLine();
            if (input.Equals("Y") || input.Equals("y")) return;
            else 
            {
               ((AlarmClock)sender).Stop = true;
               return;
            }
         }
      }
   }

   // The driver class that hooks up the event handling method of
   // WakeMeUp to the alarm event of an Alarm object using a delegate.
   // In a forms-based application, the driver class is the
   // form.
   //
   public class AlarmDriver
   {  
      public static void Main (string[] args)
      {  
         // Instantiates the event receiver.
         WakeMeUp w= new WakeMeUp();

         // Instantiates the event source.
         AlarmClock clock = new AlarmClock();

         // Wires the AlarmRang method to the Alarm event.
         clock.Alarm += new AlarmEventHandler(w.AlarmRang);

         clock.Start();
      }
   }   
}

引发多个事件

如果您的类引发多个事件,并且您按引发事件中的说明对这些事件进行编程,编译器将为每个事件委托实例生成一个字段。如果事件的数目很大,则一个委托一个字段的存储成本可能无法接受。对于这些情况,.NET Framework 提供一个称为事件属性的构造(Visual Basic 2005 中的自定义事件),此构造可以和(您选择的)另一数据结构一起用于存储事件委托。

事件属性由带事件访问器的事件声明组成。事件访问器是您定义的方法,用以允许事件委托实例添加到存储数据结构或从存储数据结构移除。请注意,事件属性要比事件字段慢,这是因为必须先检索每个事件委托,然后才能调用它。这是内存和速度之间的折中方案。如果您的类定义了许多不常引发的事件,那么您可能要实现事件属性。Windows 窗体控件和 ASP.NET 服务器控件使用事件属性而不是事件字段。

如何:使用事件属性处理多个事件

要使用事件属性(Visual Basic 2005 中的自定义事件),请在引发事件的类中定义事件属性,然后在处理事件的类中设置事件属性的委托。要在一个类中实现多个事件属性,该类必须在内部存储和维护为每个事件定义的委托。一种典型方法是实现通过事件键进行索引的委托集合。

若要存储每个事件的委托,可以使用 EventHandlerList 类或实现您自己的集合。集合类必须提供用于基于事件键设置、访问和检索事件处理程序委托的方法。例如,可以使用 Hashtable 类或从 DictionaryBase 类派生一个自定义类。不需要在类以外公开委托集合的实现详细信息。

类中的每个事件属性定义一个 add 访问器方法和一个 remove 访问器方法。事件属性的 add 访问器将输入委托实例添加到委托集合。事件属性的 remove 访问器从委托集合中移除输入委托实例。事件属性访问器使用事件属性的预定义键在委托集合中添加和从委托集合中移除实例。

使用事件属性处理多个事件

  1. 在引发事件的类中定义一个委托集合。
  2. 定义每个事件的键。
  3. 在引发事件的类中定义事件属性。
  4. 使用委托集合实现事件属性的 add 访问器方法和 remove 访问器方法。
  5. 使用公共事件属性可在处理事件的类中添加和移除事件处理程序委托。
下面的 C# 示例使用 EventHandlerList 存储每个事件的委托,从此来实现事件属性 MouseDown 和 MouseUp。事件属性构造的关键字用粗体表示。
// The class SampleControl defines two event properties, MouseUp and MouseDown.
class SampleControl: Component {
   // :
   // Define other control methods and properties.
   // :

   // Define the delegate collection.
   protected EventHandlerList listEventDelegates = new EventHandlerList();

   // Define a unique key for each event.
   static readonly object mouseDownEventKey = new object();
   static readonly object mouseUpEventKey = new object();

   // Define the MouseDown event property.
   public event MouseEventHandler MouseDown {  
      // Add the input delegate to the collection.
      add { listEventDelegates.AddHandler(mouseDownEventKey, value); }
      // Remove the input delegate from the collection.
      remove { listEventDelegates.RemoveHandler(mouseDownEventKey, value); }
   }

   // Define the MouseUp event property.
   public event MouseEventHandler MouseUp {
      // Add the input delegate to the collection.
      add { listEventDelegates.AddHandler(mouseUpEventKey, value); }
      // Remove the input delegate from the collection.
      remove { listEventDelegates.RemoveHandler(mouseUpEventKey, value); }
   }
}

 

C#委托和事件(Delegate、Event、EventHandler、EventArgs)

14.1、委托

当要把方法作为实参传送给其他方法的形参时,形参需要使用委托。委托是一个类型,是一个函数指针类型,这个类型将该委托的实例化对象所能指向的函数的细节封装起来了,即规定了所能指向的函数的签名,也就是限制了所能指向的函数的参数和返回值。当实例化委托的时候,委托对象会指向某一个匹配的函数,实质就是将函数的地址赋值给了该委托的对象,然后就可以通过该委托对象来调用所指向的函数了。利用委托,程序员可以在委托对象中封装一个方法的引用,然后委托对象作为形参将被传给调用了被引用方法的代码,而不需要知道在编译时刻具体是哪个方法被调用。

一般的调用函数,我们都不会去使用委托,因为如果只是单纯的调用函数,使用委托更麻烦一些;但是如果想将函数作为实参,传递给某个函数的形参,那么形参就一定要使用委托来接收实参,一般使用方法是:在函数外面定义委托对象,并指向某个函数,再将这个对象赋值给函数的形参,形参也是该委托类型的对象变量,函数里面再通过形参来调用所指向的函数。

14.1.1、定义委托

语法如下:

delegate result-type Identifier ([parameters]);

说明:

result-type:返回值的类型,和方法的返回值类型一致

Identifier:委托的名称

parameters:参数,要引用的方法带的参数

小结:

当定义了委托之后,该委托的对象一定可以而且也只能指向该委托所限制的函数。即参数的个数、类型、顺序都要匹配,返回值的类型也要匹配。

因为定义委托相当于是定义一个新类,所以可以在定义类的任何地方定义委托,既可以在一个类的内部定义,那么此时就要通过该类的类名来调用这个委托(委托必须是public、internal),也可以在任何类的外部定义,那么此时在命名空间中与类的级别是一样的。根据定义的可见性,可以在委托定义上添加一般的访问修饰符:当委托定义在类的外面,那么可以加上public、internal修饰符;如果委托定义到类的内部,那么可以加上public、 private、 protected、internal。一般委托都是定义在类的外面的。

14.1.2、实例化委托

Identifier objectName = new Identifier( functionName )

实例化委托的实质就是将某个函数的地址赋值给委托对象。在这里:

Identifier :这个是委托名字。

objectName :委托的实例化对象。

functionName:是该委托对象所指向的函数的名字。对于这个函数名要特别注意:定义这个委托对象肯定是在类中定义的,那么如果所指向的函数也在该类中,不管该函数是静态还是非静态的,那么就直接写函数名字就可以了;如果函数是在别的类里面定义的public、internal,但是如果是静态,那么就直接用类名.函数名,如果是非静态的,那么就类的对象名.函数名,这个函数名与该对象是有关系的,比如如果函数中出现了this,表示的就是对当前对象的调用。

14.1.3、委托推断

C# 2.0用委托推断扩展了委托的语法。当我们需要定义委托对象并实例化委托的时候,就可以只传送函数的名称,即函数的地址:

Identifier objectName = functionName;

这里面的functionName与14.1.2节中实例化委托的functionName是一样的,没什么区别,满足上面的规则。

C#编译器创建的代码是一样的。编译器会用objectName检测需要的委托类型,因此会创建Identifier委托类型的一个实例,用functionName即方法的地址传送给Identifier的构造函数。

注意:

不能在functionName后面加括号和实参,然后把它传送给委托变量。调用方法一般会返回一个不能赋予委托变量的普通对象,除非这个方法返回的是一个匹配的委托对象。总之:只能把相匹配的方法的地址赋予委托变量。

委托推断可以在需要委托实例化的任何地方使用,就跟定义普通的委托对象是一样的。委托推断也可以用于事件,因为事件基于委托(参见本章后面的内容)。

14.1.4、匿名方法

到目前为止,要想使委托工作,方法必须已经存在。但实例化委托还有另外一种方式:即通过匿名方法。

用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化委托时,就有区别了。下面是一个非常简单的控制台应用程序,说明了如何使用匿名方法:

using System;

namespace Wrox.ProCSharp.Delegates
{

    class Program
    {

        delegate string DelegateTest(string val);

        static void Main()
        {

            string mid = ", middle part,";

            //在方法中定义了方法

            DelegateTest anonDel = delegate(string param)
            {

                param += mid;

                param += " and this was added to the string.";

                return param;

            };

            Console.WriteLine(anonDel("Start of string"));

        }

    }

}
using System;

namespace Wrox.ProCSharp.Delegates
{

    class Program
    {

        delegate string DelegateTest(string val);

        static void Main()
        {

            string mid = ", middle part,";

            //Lamada表示法

            DelegateTest anonDel = ( param)=>
            {

                param += mid;

                param += " and this was added to the string.";

                return param;

            };

            Console.WriteLine(anonDel("Start of string"));

        }

    }

}

委托DelegateTest在类Program中定义,它带一个字符串参数。有区别的是Main方法。在定义anonDel时,不是传送已知的方法名,而是使用一个简单的代码块:它前面是关键字delegate,后面是一个参数:

            DelegateTest anonDel = delegate(string param)
            {

                param += mid;

                param += " and this was added to the string.";

                return param;

            };

匿名方法的优点是减少了要编写的代码。方法仅在有委托使用时才定义。在为事件定义委托时,这是非常显然的。(本章后面探讨事件。)这有助于降低代码的复杂性,尤其是定义了好几个事件时,代码会显得比较简单。使用匿名方法时,代码执行得不太快。编译器仍定义了一个方法,该方法只有一个自动指定的名称,我们不需要知道这个名称。

在使用匿名方法时,必须遵循两个规则:

1、在匿名方法中不能使用跳转语句跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。

2、在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。方法内部的变量、方法的参数可以任意的使用。

如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。而编写一个指定的方法比较好,因为该方法只需编写一次,以后可通过名称引用它。

14.1.5、多播委托

前面使用的每个委托都只包含一个方法调用,调用委托的次数与调用方法的次数相同,如果要调用多个方法,就需要多次给委托赋值,然后调用这个委托。

委托也可以包含多个方法,这时候要向委托对象中添加多个方法,这种委托称为多播委托,多播委托有一个方法列表,如果调用多播委托,就可以连续调用多个方法,即先执行某一个方法,等该方法执行完成之后再执行另外一个方法,这些方法的参数都是一样的,这些方法的执行是在一个线程中执行的,而不是每个方法都是一个线程,最终将执行完成所有的方法。

如果使用多播委托,就应注意对同一个委托调用方法链的顺序并未正式定义,调用顺序是不确定的,不一定是按照添加方法的顺序来调用方法,因此应避免编写依赖于以特定顺序调用方法的代码。如果要想确定顺序,那么只能是单播委托,调用委托的次数与调用方法的次数相同。

多播委托的各个方法签名最好是返回void;否则,就只能得到委托最后调用的一个方法的结果,而最后调用哪个方法是无法确定的。

多播委托的每一个方法都要与委托所限定的方法的返回值、参数匹配,否则就会有错误。

我自己写代码测试,测试的结果目前都是调用顺序和加入委托的顺序相同的,但是不排除有不同的时候。

delegate result-type Identifier ([parameters]);

14.1.5.1、委托运算符 =

Identifier objectName = new Identifier( functionName);

或者

Identifier objectName = functionName;

这里的“=”号表示清空 objectName 的方法列表,然后将 functionName 加入到 objectName 的方法列表中。

14.1.5.2、委托运算符 +=

objectName += new Identifier( functionName1);

或者

objectName += functionName1;

这里的“+=”号表示在原有的方法列表不变的情况下,将 functionName1 加入到 objectName 的方法列表中。可以在方法列表中加上多个相同的方法,执行的时候也会执行完所有的函数,哪怕有相同的,就会多次执行同一个方法。

注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

Identifier objectName += new Identifier( functionName1);或者

Identifier objectName += functionName1;就会报错。

14.1.5.3、委托运算符 -=

objectName -= new Identifier(functionName1);

或者

objectName -= functionName1;

这里的“-=”号表示在 objectName 的方法列表中减去一个functionName1。可以在方法列表中多次减去相同的方法,减一次只会减一个方法,如果列表中无此方法,那么减就没有意义,对原有列表无影响,也不会报错。

注意:objectName 必须是已经赋值了的,否则在定义的时候直接使用该符号:

Identifier objectName -= new Identifier( functionName1);或者

Identifier objectName -= functionName1;就会报错。

14.1.5.4、委托运算符 +、-

Identifier objectName = objectName + functionName1 - functionName1;

或者

Identifier objectName = new Identifier(functionName1) + functionName1 - functionName1;

对于这种+、-表达式,在第一个符号+或者-的前面必须是委托而不能是方法,后面的+、-左右都随便。这个不是绝对规律,还有待进一步的研究。

14.1.5.5、多播委托的异常处理

通过一个委托调用多个方法还有一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。下面是MulticastIteration示例。其中定义了一个简单的委托DemoDelegate,它没有参数,返回void。这个委托调用方法One()和Two(),这两个方法满足委托的参数和返回类型要求。注意方法One()抛出了一个异常:

using System;

namespace Wrox.ProCSharp.Delegates
{

    public delegate void DemoDelegate();

    internal class Program
    {
        private static void One()
        {

            Console.WriteLine("One");

            throw new Exception("Error in one");

        }

        private static void Two()
        {

            Console.WriteLine("Two");

        }

        static void Main()
        {

            DemoDelegate d1 = One;

            d1 += Two;

            try
            {

                d1();

            }

            catch (Exception)
            {

                Console.WriteLine("Exception caught");

            }
            Console.ReadKey();
        }

    }

}

在Main()方法中,创建了委托d1,它引用方法One(),接着把Two()方法的地址添加到同一个委托中。调用d1委托,就可以调用这两个方法。异常在try/catch块中捕获。

委托只调用了第一个方法。第一个方法抛出了异常,所以委托的迭代会停止,不再调用Two()方法。当调用方法的顺序没有指定时,结果会有所不同。

One

Exception Caught

注意:

多播委托包含一个逐个调用的委托集合。如果通过委托调用的一个方法抛出了异常,整个迭代就会停止。即如果任一方法引发了异常,而在该方法内未捕获该异常,则该异常将传递给委托的调用方,并且不再对调用列表中后面的方法进行调用。

在这种情况下,为了避免这个问题,应手动迭代方法列表。Delegate类定义了方法GetInvocationList(),它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。

using System;

namespace Wrox.ProCSharp.Delegates
{

    public delegate void DemoDelegate();

    internal class Program
    {
        private static void One()
        {

            Console.WriteLine("One");

            throw new Exception("Error in one");

        }

        private static void Two()
        {

            Console.WriteLine("Two");

        }

        static void Main()
        {

            DemoDelegate d1 = One;

            d1 += Two;

            Delegate[] delegates = d1.GetInvocationList();

            foreach (DemoDelegate d in delegates)
            {

                try
                {

                    d();

                }

                catch (Exception)
                {

                    Console.WriteLine("Exception caught");

                }

            }
            Console.ReadKey();
        }
    }

}

修改了代码后运行应用程序,会看到在捕获了异常后,将继续迭代下一个方法。

One

Exception caught

Two

注意:其实如果在多播委托的每个具体的方法中捕获异常,并在内部处理,而不抛出异常,一样能实现多播委托的所有方法执行完毕。这种方式与上面方式的区别在于这种方式的宜昌市在函数内部处理的,上面那种方式的异常是在函数外面捕获并处理的。

14.1.6、通过委托对象来调用它所指向的函数

1、委托实例的名称,后面的括号中应包含调用该委托中的方法时使用的参数。

2、调用委托对象的Invoke()方法,Invoke后面的括号中应包含调用该委托中的方法时使用的参数。

注意:实际上,给委托实例提供括号与调用委托类的Invoke()方法完全相同。因为Invoke()方法是委托的同步调用方法。

注意:不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

14.2、事件

14.2.1、自定义事件

14.2.1.1、声明一个委托:

Delegate result-type delegateName ([parameters]);

这个委托可以在类A内定义也可以在类A外定义。

14.2.1.2、声明一个基于某个委托的事件

Event delegateName eventName;

eventName不是一个类型,而是一个具体的对象,这个具体的对象只能在类A内定义而不能在类A外定义。

14.2.1.3、在类A中定义一个触发该事件的方法

ReturnType FunctionName([parameters])

{

……

If(eventName != null)

{

eventName([parameters]);

或者eventName.Invoke([parameters]);

}

……

}

触发事件之后,事件所指向的函数将会被执行。这种执行是通过事件名称来调用的,就像委托对象名一样的。

触发事件的方法只能在A类中定义,事件的实例化,以及实例化之后的实现体都只能在A类外定义。

14.2.1.4、初始化A类的事件

在类B中定义一个类A的对象,并且让类A对象的那个事件指向类B中定义的方法,这个方法要与事件关联的委托所限定的方法吻合。

14.2.1.5、触发A类的事件

在B类中去调用A类中的触发事件的方法:用A类的对象去调用A类的触发事件的方法。

14.2.1.6、程序实例

using System;

using System.Collections.Generic;
using System.Globalization;
using System.Text;

using System.Threading;

namespace DelegateStudy
{

    public delegate void DelegateClick(int a);

    public class Butt
    {

        public event DelegateClick Click;

        public void OnClick(int a)
        {

            if (Click != null)
                Click.Invoke(a);

            //Click(a);//这种方式也是可以的

            Console.WriteLine("Click()");

        }

    }

    class Program
    {
        public static void Btn_Click(int a)
        {

            for (long i = 0; i < a; i++)

                Console.WriteLine(i.ToString(CultureInfo.InvariantCulture));

        }
        static void Main(string[] args)
        {

            var b = new Butt();

            //在委托中,委托对象如果是null的,直接使用+=符号,会报错,但是在事件中,初始化的时候,只能用+=

            b.Click += Btn_Click; //事件是基于委托的,所以委托推断一样适用,下面的语句一样有效:b.Click += Fm_Click;

            //b.Click(10);错误:事件“DelegateStudy.Butt.Click”只能出现在 += 或 -= 的左边(从类型“DelegateStudy.Butt”中使用时除外)

            b.OnClick(10000);

            Console.ReadLine();

        }
    }
}

14.2.2、控件事件

基于Windows的应用程序也是基于消息的。这说明,应用程序是通过Windows来与用户通信的,Windows又是使用预定义的消息与应用程序通信的。这些消息是包含各种信息的结构,应用程序和Windows使用这些信息决定下一步的操作。

比如:当用户用鼠标去点击一个windows应用程序的按钮的时候,windows操作系统就会捕获到这个点击按钮的动作,这个时候它会根据捕获到的动作发送一个与之对应的预定义的消息给windows应用程序的这个按钮,windows应用程序的按钮消息处理程序会处理接收到的消息,这个程序处理过程就是根据收到的消息去触发相应的事件,事件被按钮触发后,会通知所有的该事件的订阅者来接收这个事件,从而执行相应的的函数。

在MFC等库或VB等开发环境推出之前,开发人员必须处理Windows发送给应用程序的消息。VB和今天的.NET把这些传送来的消息封装在事件中。如果需要响应某个消息,就应处理对应的事件。

14.2.2.1、控件事件委托EventHandler

在控件事件中,有很多的委托,在这里介绍一个最常用的委托EventHandler,.NET Framework中控件的事件很多都基于该委托,EventHandler委托已在.NET Framework中定义了。它位于System命名空间:

Public delegate void EventHandler(object sender,EventArgs e);

14.2.2.2、委托EventHandler参数和返回值

事件最终会指向一个或者多个函数,函数要与事件所基于的委托匹配。事件所指向的函数(事件处理程序)的命名规则:按照约定,事件处理程序应遵循“object_event”的命名约定。object就是引发事件的对象,而event就是被引发的事件。从可读性来看,应遵循这个命名约定。

首先,事件处理程序总是返回void,事件处理程序不能有返回值。其次是参数,只要是基于EventHandler委托的事件,事件处理程序的参数就应是object和EventArgs类型:

第一个参数接收引发事件的对象,比如当点击某个按钮的时候,这个按钮要触发单击事件最终执行这个函数,那么就会把当前按钮传给sender,当有多个按钮的单击事件都指向这个函数的时候,sender的值就取决于当前被单击的那个按钮,所以可以为几个按钮定义一个按钮单击处理程序,接着根据sender参数确定单击了哪个按钮:

if(((Button)sender).Name ==”buttonOne”)

第二个参数e是包含有关事件的其他有用信息的对象。

14.2.2.3、控件事件的其他委托

控件事件还有其他的委托,比如在窗体上有与鼠标事件关联的委托:

Public delegate void MouseEventHandler(object sender,MouseEventArgs e);

public event MouseEventHandler MouseDown;

this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseDown);

private void Form1_MouseDown(object sender, MouseEventArgs e){};

MouseDown事件使用MouseDownEventArgs,它包含鼠标的指针在窗体上的的X和Y坐标,以及与事件相关的其他信息。

控件事件中,一般第一个参数都是object sender,第二个参数可以是任意类型,不同的委托可以有不同的参数,只要它派生于EventArgs即可。

14.2.2.4、程序实例

using System;

using System.Collections.Generic;

using System.Text;

using System.Threading;

namespace SecondChangeEvent1
{

    // 该类用来存储关于事件的有效信息外,

    // 还用来存储额外的需要传给订阅者的Clock状态信息

    public class TimeInfoEventArgs : EventArgs
    {

        public TimeInfoEventArgs(int hour, int minute, int second)
        {

            this.hour = hour;

            this.minute = minute;

            this.second = second;

        }

        public readonly int hour;

        public readonly int minute;

        public readonly int second;

    }

    // 定义名为SecondChangeHandler的委托,封装不返回值的方法,

    // 该方法带参数,一个clock类型对象参数,一个TimeInfoEventArgs类型对象

    public delegate void SecondChangeHandler(

    object clock,

    TimeInfoEventArgs timeInformation

    );

    // 被其他类观察的钟(Clock)类,该类发布一个事件:SecondChange。观察该类的类订阅了该事件。

    public class Clock
    {

        // 代表小时,分钟,秒的私有变量

        int _hour;

        public int Hour
        {

            get { return _hour; }

            set { _hour = value; }

        }

        private int _minute;

        public int Minute
        {

            get { return _minute; }

            set { _minute = value; }

        }

        private int _second;

        public int Second
        {

            get { return _second; }

            set { _second = value; }

        }

        // 要发布的事件

        public event SecondChangeHandler SecondChange;

        // 触发事件的方法

        protected void OnSecondChange(

        object clock,

        TimeInfoEventArgs timeInformation

        )
        {

            // Check if there are any Subscribers

            if (SecondChange != null)
            {

                // Call the Event

                SecondChange(clock, timeInformation);

            }

        }

        // 让钟(Clock)跑起来,每隔一秒钟触发一次事件

        public void Run()
        {

            for (; ; )
            {

                // 让线程Sleep一秒钟

                Thread.Sleep(1000);

                // 获取当前时间

                System.DateTime dt = System.DateTime.Now;

                // 如果秒钟变化了通知订阅者

                if (dt.Second != _second)
                {

                    // 创造TimeInfoEventArgs类型对象,传给订阅者

                    TimeInfoEventArgs timeInformation =

                    new TimeInfoEventArgs(

                    dt.Hour, dt.Minute, dt.Second);

                    // 通知订阅者

                    OnSecondChange(this, timeInformation);

                }

                // 更新状态信息

                _second = dt.Second;

                _minute = dt.Minute;

                _hour = dt.Hour;

            }

        }

    }

    /* ======================= Event Subscribers =============================== */

    // 一个订阅者。DisplayClock订阅了clock类的事件。它的工作是显示当前时间。

    public class DisplayClock
    {

        // 传入一个clock对象,订阅其SecondChangeHandler事件

        public void Subscribe(Clock theClock)
        {

            theClock.SecondChange +=

            new SecondChangeHandler(TimeHasChanged);

        }

        // 实现了委托匹配类型的方法

        public void TimeHasChanged(

        object theClock, TimeInfoEventArgs ti)
        {

            Console.WriteLine("Current Time: {0}:{1}:{2}",

            ti.hour.ToString(),

            ti.minute.ToString(),

            ti.second.ToString());

        }

    }

    // 第二个订阅者,他的工作是把当前时间写入一个文件

    public class LogClock
    {

        public void Subscribe(Clock theClock)
        {

            theClock.SecondChange +=

            new SecondChangeHandler(WriteLogEntry);

        }

        // 这个方法本来应该是把信息写入一个文件中

        // 这里我们用把信息输出控制台代替

        public void WriteLogEntry(

        object theClock, TimeInfoEventArgs ti)
        {

            Clock a = (Clock)theClock;

            Console.WriteLine("Logging to file: {0}:{1}:{2}",

            a.Hour.ToString(),

            a.Minute.ToString(),

            a.Second.ToString());

        }

    }

    /* ======================= Test Application =============================== */

    // 测试拥有程序

    public class Test
    {

        public static void Main()
        {

            // 创建clock实例

            Clock theClock = new Clock();

            // 创建一个DisplayClock实例,让其订阅上面创建的clock的事件

            DisplayClock dc = new DisplayClock();

            dc.Subscribe(theClock);

            // 创建一个LogClock实例,让其订阅上面创建的clock的事件

            LogClock lc = new LogClock();

            lc.Subscribe(theClock);

            // 让钟跑起来

            theClock.Run();

        }

    }

}

14. 3、小结

(1)、在定义事件的那个类A里面,可以任意的使用事件名,可以触发;在别的类里面,事件名只能出现在 += 或 -= 的左边来指向函数,即只能实例化,不能直接用事件名触发。但是可以通过A类的对象来调用A类中的触发事件的函数。这是唯一触发事件的方式。

(2)、不管是多播委托还是单播委托,在没有特殊处理的情况下,在一个线程的执行过程中去调用委托(委托对象所指向的函数),调用委托的执行是不会新起线程的,这个执行还是在原线程中的,这个对于事件也是一样的。当然,如果是在委托所指向的函数里面去启动一个新的线程那就是另外一回事了。

(3)、事件是针对某一个具体的对象的,一般在该对象的所属类A中写好事件,并且写好触发事件的方法,那么这个类A就是事件的发布者,然后在别的类B里面定义A的对象,并去初始化该对象的事件,让事件指向B类中的某一个具体的方法,B类就是A类事件的订阅者。当通过A类的对象来触发A类的事件的时候(只能A类的对象来触发A类的事件,别的类的对象不能触发A类的事件,只能订阅A类的事件,即实例化A类的事件),作为订阅者的B类会接收A类触发的事件,从而使得订阅函数被执行。一个发布者可以有多个订阅者,当发布者发送事件的时候,所有的订阅者都将接收到事件,从而执行订阅函数,但是即使是有多个订阅者也是单线程。

SQL with(nolock)详解

大家在写查询时,为了性能,往往会在表后面加一个nolock,或者是with(nolock),其目的就是查询是不锁定表,从而达到提高查询速度的目的。

什么是并发访问:同一时间有多个用户访问同一资源,并发用户中如果有用户对资源做了修改,此时就会对其它用户产生某些不利的影响,例如:

1:脏读

一个用户对一个资源做了修改,此时另外一个用户正好读取了这条被修改的记录,然后,第一个用户放弃修改,数据回到修改之前,这两个不同的结果就是脏读。

2:不可重复读

一个用户的一个操作是一个事务,这个事务分两次读取同一条记录,如果第一次读取后,有另外用户修改了这个数据,然后第二次读取的数据正好是其它用户修改的数据,这样造成两次读取的记录不同,如果事务中锁定这条记录就可以避免。

3:幻读

指用户读取一批记录的情况,用户两次查询同一条件的一批记录,第一次查询后,有其它用户对这批数据做了修改,方法可能是修改,删除,新增,第二次查询时,会发现第一次查询的记录条目有的不在第二次查询结果中,或者是第二次查询的条目不在第一次查询的内容中。

为什么会在查询的表后面加nolock标识?为了避免并发访问产生的不利影响,SQL Server有两种并发访问的控制机制:锁、行版本控制,表后面加nolock是解决并发访问的方案之一。

1.锁

每个事务对所依赖的资源会请求不同类型的锁,它可以阻止其他事务以某种可能会导致事务请求锁出错的方式修改资源。当事务不再依赖锁定的资源时,锁将被释放。

锁的类型:1:表类型:锁定整个表;2:行类型:锁定某个行;3:文件类型:锁定某个数据库文件;4:数据库类型:锁定整个数据库;5:页类型:锁定8K为单位的数据库页。

锁的分类还有一种分法,就是按用户和数据库对象来分:

1). 从数据库系统的角度来看:分为独占锁(即排它锁),共享锁和更新锁

1:共享 (S) :用于不更改或不更新数据的操作(只读操作),一般常见的例如select语句。

2:更新 (U) :用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。

3:排它 (X) :用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。

2). 从程序员的角度看:分为乐观锁和悲观锁。

1:乐观锁:完全依靠数据库来管理锁的工作。

2:悲观锁:程序员自己管理数据或对象上的锁处理。

一般程序员一看到什么锁之类,觉的特别复杂,对专业的DBA当然是入门级知识了。可喜的是程序员不用去设置,控制这些锁,SQLServer通过设 置事务的隔离级别自动管理锁的设置和控制。锁管理器通过查询分析器分析待执行的sql语句,来判断语句将会访问哪些资源,进行什么操作,然后结合设定的隔 离级别自动分配管理需要用到的锁。

2.行版本控制

当启用了基于行版本控制的隔离级别时,数据库引擎 将维护修改的每一行的版本。应用程序可以指定事务使用行版本查看事务或查询开始时存在的数据,而不是使用锁保护所有读取。通过使用行版本控制,读取操作阻 止其他事务的可能性将大大降低。也就是相当于针对所有的表在查询时都会加上nolock,同样会产生脏读的现象,但差别在于在一个统一管理的地方。说到了基于行版本控制的隔离级别,这里有必要说下隔离级别的概念。

隔离级别的用处:控制锁的应用,即什么场景应用什么样的锁机制。

最终目的:解决并发处理带来的种种问题。

隔离级别的分类:

1:未提交读,隔离事务的最低级别,只能保证不读取物理上损坏的数据;

2:已提交读,数据库引擎的默认级;

3:可重复读;

4:可序列化;隔离事务的最高级别,事务之间完全隔离。

小结:NOLOCK 语句执行时不发出共享锁,允许脏读 ,等于 READ UNCOMMITTED事务隔离级别 。nolock确实在查询时能提高速度,但它并不是没有缺点的,起码它会引起脏读。

nolock的使用场景(个人观点):

1:数据量特别大的表,牺牲数据安全性来提升性能是可以考虑的;

2:允许出现脏读现象的业务逻辑,反之一些数据完整性要求比较严格的场景就不合适了,像金融方面等。

3:数据不经常修改的表,这样会省于锁定表的时间来大大加快查询速度。

最后说下nolock和with(nolock)的几个小区别:

1:SQL05中的同义词,只支持with(nolock);

2:with(nolock)的写法非常容易再指定索引。

跨服务器查询语句时 不能用with (nolock) 只能用nolock,同一个服务器查询时 则with (nolock)和nolock都可以用

SQL Server中的Transaction、error check、Lock、Isolation level、save point

Transaction及错误检查

SQL Server 中最重要的知识点莫过于事务,比如很多OLTP(联机事务处理)应用程序。什么是事务?事务就是一系列SQL语句的集合。事务包括隐性事务(例如Insert,Update等语句)和显性事务(用Begin Tran语句显式指明的事务)。事务中通常需要进行错误检查,用@@error来进行检查,比如:

Begin Tran
Update A set id =5 where id=1
If @@error<>0
rollback Tran
Update A set id =5 where id=2
If @@error<>0
rollback Tran
Commit Tran

Lock (锁)

按锁的粒度分,锁可以分成如下几类:

Key Lock(键锁)—>Row Lock(行级锁)—>Page Lock(页级锁)—>Extent Lock(扩展盘曲锁)—>Table Lock(表锁)—>Database Lock(数据库锁)

按锁的模式分,锁可以分为如下几类:

holdlock(共享锁),xlock(排它锁),Updlock(更新锁),Schlock(架构锁),Intent lock(意向锁)等等。如果要查看锁的类型,使用系统存储过程sys_lock来查看。

Isolation level (隔离级别)

事务有4中隔离级别,分别为:
read uncommitted(未提交读) — 读未提交,可以读取到内存中已经修改但是没有保存到硬盘上的信息,即允许数据脏读。
read committed(提交读) — 读提交,只能读取到已经提交到硬盘的信息,如果信息在内存中修改了,但是还没有提交到硬盘,即没有commit tran,则另一个事务什么也读取不到,被另一事物阻塞在此。当修改数据的事务一旦commit tran,则读取数据的事务立即运行,修改后的数据被读取到。
repeatable read(重复读) — 当事务A设置隔离级别为repeatable read在对数据进行读取,此时,事务B来修改数据,由于repeatable read隔离级别对操作的实体(行或者表)设置了更新锁,所以此时事务B不能对数据进行更新,但是事务B可以insert新数据,因为repeatable read隔离级别对操作的实体(行或者表)没有设置排它锁,所以事务A可以读取到幻象。
serializable(串行读) — 串行化,即事务一个接一个地进行操作,包括对操作实体的update,insert,select等等。

实际上隔离级别和锁的关系是密不可分的,隔离级别的实现本质上是对锁来进行操作,由于我们在操作一个实体对象的时候不能准确地判断到底应该上什么具体的锁 ,所以鉴于此,SQL server数据库为我们开辟了一个简单的途径,即使用隔离级别。实体的隔离级别越高,说明实体上锁的数量越多,种类越复杂;实体的隔离级别越高,并行化的几率越低,串行化的几率越高。

Save Point(保存点)

保存点的出现,是为了在事务恢复时更加地迅速和容易,因为不用把所有的操作都恢复,而是只用恢复到保存点即可,关于如何恢复以及更具体的知识,会在事务的恢复博客中详述。举个简单的例子:

Begin Tran
Update A set id =4 where id=1
Save tran t1
Update A set id =3 where id=2
If @@error<>0
rollback t1
Update A set id=5 where id =3
Commit Tran