C#中的委托数据类型简介

什么是委托?

委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。通俗的说,委托是一个可以引用方法的对象,当创建一个委托,也就创建一个引用方法的对象,进而就可以调用那个方法,即委托可以调用它所指的方法。

来看下面的例子,类deleMthod定义了3个方法,add、minus和multi,他们都具有相同的输入参数列表(int x,int y)和输出参数类型int,那么我们就说这三个方法具有相同的方法签名。开发者可以抽象地用 int 某名称(int x,int y) 的一种类型对方法进行封装,在c#中这种抽象的数据类型叫委托,针对上述的几个方法我们可以定义委托 : public delegate int Handler(int x ,int y),public 是一个访问修饰符,delegate关键字表示这是一个委托,int Hander(int x,int y)表示这个委托的名称。

class deleMethod
{
public int add(int x, int y)
{
return x + y;
}
public int minus(int x, int y)
{
return x - y;
}
public int multi(int x, int y)
{
return x * y;
}
}

怎么使用委托

使用委托大体有四个步骤:

•定义一个委托,上节已经提及。

•定义委托方法,上节deleMethod类中add、minus、multi都是委托方法,定义的目的就是为了使用它,讲专业点就是为了方法的调用

•委托变量及赋值,和类型一样,在使用前需要对变量赋值。

•委托变量的使用。

怎样定义委托变量,还是接着上面的例子。我们已经定义了一个委托类型 public delegate int Handler(int x,int y),和c#语法规范一样定义一个变量并赋值语法是:“类型名 变量名 = new 类型名(方法);”,如上例

“Handler deleCall = new Handler(方法名);“,在.net2.0后对于委托的实例化可以简化为” Handler deleCall = 方法名;“。

委托变量的使用,也就是对委托方法的调用。其语法是”int result1 = deleCall(10,20);“或者使用委托调用方法 Invoke,“int result2 = deleCall.Invoke(10,20);”。

具体如何使用可以看看下面的示例:

class Program
{
public delegate int Handler(int x, int y); //---定义委托的类型,可以将委托看成一种特殊的数据类型
static void Main(string[] args)
{
deleMethod dm = new deleMethod(); //---实例化包含委托方法的类型
Handler deleCall = new Handler(dm.add); //---定义委托变量delCall,并出示化赋值
int result1 = deleCall(10, 20); //---实例方法的调用invoke
Console.WriteLine("the add resutl is:{0}", result1);
deleCall = dm.minus;
int result2 = deleCall.Invoke(12, 6);
Console.WriteLine("the minus result is:{0}", result2);
Console.ReadLine();
}
} 

如上例所示,定义一个简单的加、减功能如此的复杂,搅来搅去让人头,真是无语,难怪很多朋友谈委托色变晕。在实际使用的过程中,c#还是有很多方式帮我们简化代码。

简化委托

预定义的泛型委托

c#系统最常见的预定义的委托类型有三种,Func<>委托、Action<>委托、Predicate<>委托,Func<>委托是一个有返回值的委托,输入参数可以多达16个;而Action<>委托是一个没有返回值的委托,它的输入参数也可以多达16个;而Predicate<>是一个具有bool返回类型的委托,它只运行一个输入参数。对于有上例的委托类型,我们可以使用预定义的委托类型Fun<int,int,int>来代替,省去我们自己定义一个什么鬼东西 public delegate int Handler(int x,int y)类型,其代码其实可以简化为如下例所示:

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
deleMethod dm = new deleMethod();
Func<int, int, int> fun = dm.add; //---使用预定义的委托类型Func<>
int result4 = fun(8, 10);
Func<int, int, int> fun1 = dm.minus;
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
class deleMethod
{
public int add(int x, int y)
{
return x + y;
}
public int minus(int x, int y)
{
return x - y;
}
public int multi(int x, int y)
{
return x * y;
}
}
}

我把委托的方法定义和委托的调用放在一起看,是不是比原先自己定义的一个委托类型简单方便一些?但是这样使用委托还是不怎么清爽,估计在实际应用中很少人会怎么写代码,太不方便了。

匿名委托

当委托实例的调用和委托方法的定义分开处理,码农们在读程序代码的时候需要来回的去找委托方法去匹配委托变量,看看参数列表和返回值是否正确,这样的程序代码的可读性很差。其实c#还是有方法让我们简化代码:那就是匿名委托,将方法体直接在委托的实例化时给出,之所以叫匿名委托就是再定义委托的时候省略掉委托的名称,它的定义语法是delegate(参数1,参数2) 后面直接就给出方法体,用大括号将方法体括起。刚看起来比较怪异,接触多了也就习惯了,莫有办法只能去适应c#的语法规范。话说多了是水,还不如看代码来得直接。

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> fun = delegate(int x, int y) { return x + y; };
Func<int, int, int> fun1 = delegate(int x, int y) { return x - y; };
int result4 = fun(8, 10);
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
} 

看看是不是在原来的基础上大幅度减少了代码量,肿么办,是否代码量已经减少到极致了?

lambda表达式

其实对于委托的定义还可以进一步简化,那就是使用lambda表达式,lambda表达式的定义是(参数列表)=>{方法体},=>读作goes to。lambda表达式对参数列表和方法表达式的精简达到极致,对于上面的例子,用λ表达式可以省略掉匿名委托的关键字和参数类型,系统可以进行类型推断,不影响运行,其简化的代码如下。对.net 1.0,2.0最传统的委托定义和使用,是一个巨大的简化,它剔除了所有多余的语句达到极致。

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> fun = (x, y) => x + y;
Func<int, int, int> fun1 = (x, y) => x - y;
int result4 = fun(8, 10);
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
} 

委托链

前面讲过,委托在本质上仍然是一个类,我们用delegate关键字声明的所有委托都继承自System.MulticastDelegate。后者又是继承自System.Delegate类,System.Delegate类则继承自System.Object。一个委托可以绑定若干相同签名的方法形成一个委托链,委托链是一个委托实例的集合,它允许我们调用这个集合中的委托实例所代表的所有方法(对于有返回值的方法,委托链的返回值为链表中最后一个方法的返回值),在下面的例子我们定义的委托方法都没有返回值。我们可以用 GetInvocationList()方法获取委托链。

class Program
{
static void Main(string[] args)
{
//Action 表示没有返回值的一类方法
Action<int, int> actionA = (x, y) =>
{
Console.WriteLine("x是{0},y是{1},他们的平方和是{2}", x, y, x * x + y * y);
};
actionA += (x, y) =>
{
Console.WriteLine("x是{0},y是{1},他们的平方差是{2}", x, y, x * x - y * y);
};
actionA(10, 5);
foreach (var item in actionA.GetInvocationList())
Console.WriteLine(item.Method);
Console.ReadLine();
}
} 

什么是事件

经常看到一种定义是:事件是一种特殊的委托,是对委托的封装。其实这种定义是很不严谨的。委托是一种数据类型,但是事件只是委托的实例,不能算是一种数据类型,所以说事件是一种特殊的委托是不准确的。如果这样定义:事件是一种特殊的委托实例,是对委托的封装。那么在C#中事件是如何定义并被使用的呢?其实事件从定义到使用要经过四个阶段。

•定义事件依赖的委托,并定义事件以及引发事件的方法,该步骤可以定义在事件发布器类中

•定义事件所依赖的事件方法,该步骤可以定义在事件订阅者类中

•如果有事件参数,该步骤可以定义在事件参数类中

•注册事件并引发事件,该步骤一般写在主程序中

一般来讲在事件定义过程中,各个方法的命名没有统一的标准,但我们可以参考微软在c#中的命名规范进行命名

建议事件委托命名为:事件+EventHandler。如果有额外的参数传递需定义自己的参数类型,参数类型的命名规范 :事件+EventHandler。比如,可以定义一个事件委托 public delegate void calcEventHandler(object sender,calcEventArgs e); 。

定义一个事件变量如:public event calcEventHandler calc;

定义一个引发事件的方法如:public void onCalc(object sender,calcEventArgs e){}

建议方法名为 on+事件,就如同我们在开发web程序时,绑定的事件名有onClick,onLoad等一样。

参考看下面的例子,了解定义一个事件的一般过程,如果不需要传递事件的参数可以省去事件参数类的定义,使用系统预定义的EventArgs就可以了。

/// <summary>
/// 主程序类
/// </summary>
class Program
{
static void Main(string[] args)
{
eventPublish myEvent = new eventPublish();
eventSubscription myMethod = new eventSubscription();
//绑定方法 add 和 subtract,又称为对事件方法的注册 -=称为对事件方法的注销
myEvent.calc += myMethod.add;
myEvent.calc += myMethod.substract;
while (true)
{
try
{
Console.WriteLine("请输入第一个整数数");
int numA = Convert.ToInt16(Console.ReadLine());
Console.WriteLine("请输入第二个整数");
int numB = Convert.ToInt16(Console.ReadLine());
calcEventArgs e = new calcEventArgs(numA, numB);
//在本例不需要sender参数,随意传递了字符串"sender"
myEvent.onCalc("sender", e);
}
catch (Exception ex)
{
Console.WriteLine("出现错误," + ex.Message);
}
}
}
}
/// <summary>
/// 定义一个事件发布器类
/// </summary>
class eventPublish
{
//定义一个委托类型,委托名一般是 事件变量名+EventHandler
public delegate void calcEventHander(object sender,calcEventArgs e);
//定义一个事件变量,其变量名为calc
public event calcEventHander calc;
//封装事件,对外定义了引发事件的方法,定义的引发方法名一般是 on+事件变量名
public void onCalc(object sender, calcEventArgs e)
{
if (calc != null)
calc(sender,e);
}
}
/// <summary>
/// 定义一个事件订阅者类(事件方法类)
/// </summary>
class eventSubscription
{
public void add(object sender, calcEventArgs e)
{
Console.WriteLine("两数相加等于{0}", e.X + e.Y );
}
public void substract(object sender, calcEventArgs e)
{
Console.WriteLine("两数相减等于{0}", e.X - e.Y);
}
}
/// <summary>
/// 定义一个事件参数类
/// </summary>
class calcEventArgs : EventArgs
{
private int _x;
private int _y;
public calcEventArgs(int x, int y)
{
this._x = x;
this._y = y;
}
public int X
{
get { return _x; }
}
public int Y
{
get { return _y; }
}
}

我们将事件是对委托的封装,如果用ILDAS反编译可执行文件查看中间代码就可以非常明了的看出事件运行机制

如上图所示,事件calc,经.net运行时编译为中间语言后产生了一个private 的calc属性,同时也自动生成了add_calc和remove_calc方法,用于注册和注销订阅者方法。因为事件是被封装的,尽管calc属性是private,但在所以在发布器类内部可以用 calc(sender,e)这样的方法直接调用;但在主程序中如果这样使用就会出错,只能通过onCalc方法进行间接调用。

后记

  本篇文章,一开始提出了什么是委托的疑问,通过引入几个方法来讲述委托是什么以加强对委托概念的理解。第二部分讲述了使用委托的四个步骤,并通过示例阐述了这几个步骤。第三部分讲述了委托使用的简化问题,通过使用泛型委托简化自定义委托,通过使用匿名委托可以简化定义委托方法。匿名委托是在定义委托的时候直接给出方法体,通过使用lambda表达式的类型推断可进一步简化委托的使用。第四部分讲述了委托链,通过绑定方法初始化委托,并通过+=绑定更多的委托方法。第五部分讲述了事件的定义和使用的四个步骤。当然委托的使用场景还有很多,比如通过BeginInvoke和EndInvoke进行异步调用,因不是本篇文章的重点,所以文章中没有提及。

以上内容是小编给大家介绍的C#中的委托数据类型简介,希望对大家有所帮助!

(0)

相关推荐

  • C#使用LINQ中Enumerable类方法的延迟与立即执行的控制

    延时执行的Enumerable类方法 LINQ标准查询运算法是依靠一组扩展方法来实现的.而这些扩展方法分别在System.Linq.Enumerable和System.Linq.Queryable这连个静态类中定义. Enumerable的扩展方法采用线性流程,每个运算法会被线性执行.这种执行方法如果操作类似关系型数据库数据源,效率会非常低下,所以Queryable重新定义这些扩展方法,把LINQ表达式拆解为表达式树,提供程序就可以根据表达式树生成关系型数据库的查询语句,即SQL命令,然后进行相

  • C#使用Process类调用外部exe程序

    在编写程序时经常会使用到调用可执行程序的情况,本文将简单介绍C#调用exe的方法.在C#中,通过Process类来进行进程操作. Process类在System.Diagnostics包中. 示例一 复制代码 代码如下: using System.Diagnostics; Process p = Process.Start("notepad.exe"); p.WaitForExit();//关键,等待外部程序退出后才能往下执行 通过上述代码可以调用记事本程序,注意如果不是调用系统程序,

  • C#实现动态生成静态页面的类详解

    本文实例讲述了C#实现动态生成静态页面的类.分享给大家供大家参考,具体如下: 动态生成静态页面有许多好处,比如生成html网页有利于被搜索引擎收录.同时,由于减少了数据访问,减轻对数据库访问的压力,提高了网页打开速度. 基本思路: 使用一个字符串作为页面模板,再页面中包含用若干标志(用 {标志名} 表示),生成页面时,将标志替换为对应的值. 实现方法: 在初始化TextTemplate实例时读入模板,以标志为分割点将模板分割成几部分,生成页面时只需简单的将模板内容和标志的值连接起来.例如: 假如

  • 结合.net框架在C#派生类中触发基类事件及实现接口事件

    在派生类中引发基类事件 以下简单示例演示了在基类中声明可从派生类引发的事件的标准方法.此模式广泛应用于 .NET Framework 类库中的 Windows 窗体类. 在创建可用作其他类的基类的类时,应考虑如下事实:事件是特殊类型的委托,只可以从声明它们的类中调用.派生类无法直接调用基类中声明的事件.尽管有时需要事件仅由基类引发,但在大多数情形下,应该允许派生类调用基类事件.为此,您可以在包含该事件的基类中创建一个受保护的调用方法.通过调用或重写此调用方法,派生类便可以间接调用该事件. 注意:

  • C#中的静态成员、静态方法、静态类介绍

    1.静态成员.实例成员 1.1定义及说明 数据成员: 静态成员:静态成员变量是和类相关联的,可以作为类中"共"有的变量(是一个共性的表现),他不依赖特定对象的存在,访问的时候通过类名加点操作符加变量名来访问. 实例成员:实例成员变量是和对象相关联的,访问实例成员变量依赖于实例的存在. 函数成员: 静态方法:静态方法是不属于特定对象的方法,静态方法可以访问静态成员变量和静态方法:静态方法不可以直接访问实例变量和实例方法,可以间接调用,首先要创建一个类的实例,然后通过这一特定对象来调用静态

  • C#中使用XmlDocument类来创建和修改XML格式的数据文件

    通过XmlDocument类修改XML文档数据,通常需要以下几个主要步骤或其中几个步骤. (1)获取一个包含XML文档数据的XmlDocument类对象,通常有两种方法来实现这个功能: 通过XmlDocument类的构造函数创建不包含任何结点的空对象,常用默认构造函数. (2)通过XmlDocument类的ChildNodes和Item属性获取某个结点(XmlNode类型),通过XmlNode的Name.Value.InnerText等属性修改选中结点的数据. (3)通过XmlDocument类

  • C#编程中枚举类型的使用教程

    枚举类型(也称为枚举)为定义一组可以赋给变量的命名整数常量提供了一种有效的方法.例如,假设您必须定义一个变量,该变量的值表示一周中的一天.该变量只能存储七个有意义的值.若要定义这些值,可以使用枚举类型.枚举类型是使用 enum关键字声明的. enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday }; enum Months : byte { Jan, Feb, Mar, Apr, May, Jun,

  • 分享C#中几个可用的类

    本文实例为大家介绍了几个可用的类,供大家参考,具体内容如下 1.SQLHelper类 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data.SqlClient; using System.Data; using System.Configuration; namespace MySchool.DAL { public static class

  • C#简单邮件群发通用类

    本文实例为大家介绍了C#邮件群发通用类,供大家参考,具体内容如下 public static class Email { /// <summary> /// 发件人 /// </summary> public static string mailFrom { get; set; } /// <summary> /// 收件人 /// </summary> public static string[] mailToArray { get; set; } ///

  • ASP.NET在底层类库中获取Session C#类中获取Session 原创

    类库中获取Session首先要添加引用 获取Session 复制代码 代码如下: string user = (string)HttpContext.Current.Session["user"]; 获取Page 复制代码 代码如下: System.Web.UI.Page page = (System.Web.UI.Page)HttpContext.Current.Handler; 获取当前 Request Response 等对象都是在这里 复制代码 代码如下: HttpRespon

随机推荐