在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
委托给了C#操作函数的灵活性,我们可使用委托像操作变量一样来操作函数,其实这个功能并不是C#的首创,早在C++时代就有函数指针这一说法,而在我看来委托就是C#的函数指针,首先先简要的介绍一下委托的基本知识:
用匿名函数初始化委托 上面为了初始化委托要定义一个函数是不是感觉有点麻烦,另外被赋予委托的函数一般都是通过委托实例来调用,很少会直接调用函数本身。 在.net 2.0的时候考虑到这种情况,于是匿名函数就诞生了,由于匿名函数没有名字所以必须要用一个委托实例来引用它,定义匿名函数就是为了初始化委托 匿名函数初始化委托的原型: <委托类型> <实例化名>=new <委托类型>(delegate(<函数参数>){函数体}); 当然在.net 2.0后可以用: <委托类型> <实例化名>=delegate(<函数参数>){函数体}; 例子:
delegate void Func1(int i);
delegate int Func2(int i); static Func1 t1 =new Func1(delegate(int i) { Console.WriteLine(i); }); static Func2 t2; static void Main(string[] args) { t2 = delegate(int j) { return j; }; t1(2); Console.WriteLine(t2(1)); }
当然在.net 3.0的时候又有了比匿名函数更方便的东西lambda表达式,这儿就不说了。
static int test(int t) static void Main(string[] args)
另外当在委托和事件(事件的细节将在后面介绍)上注册了多个函数后,如果委托和事件有返回值,那么调用委托和事件时,返回的将是最后一个注册函数的返回值。如下示例代码将做详细解释。 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MultiDelegatesReturn
{
public delegate int DelMath(int i);//定义委托类DelMath,该委托传入一个int类型参数,返回一个int类型参数
class Program
{
static DelMath dMath;//通过委托类型DelMath定义委托实例dMath
static event DelMath eMath;//通过委托类型DelMath定义事件实例eMath
/// <summary>
/// 将传入的参数i自加后作为函数返回值
/// </summary>
static int IncMath(int i)
{
i++;
Console.WriteLine("IncMath has been invoked!");
return i;
}
/// <summary>
/// 将传入的参数i自减后作为函数返回值
/// </summary>
static int DecMath(int i)
{
i--;
Console.WriteLine("DecMath has been invoked!");
return i;
}
static void Main(string[] args)
{
int i = 10;//定义int型变量i,初始值为10
dMath += IncMath;//先将IncMath函数注册到委托实例dMath
dMath += DecMath;//再将DecMath函数注册到委托实例dMath
Console.WriteLine("dMath returned:" + dMath(i).ToString());//将int型变量10传入委托实例dMath调用后,返回的结果是9,说明委托实例
//dMath返回的是后注册的函数DecMath的返回值
eMath += IncMath;//先将IncMath函数注册到事件实例eMath
eMath += DecMath;//再将DecMath函数注册到事件实例eMath
Console.WriteLine("eMath returned:" + eMath(i).ToString());//将int型变量10传入事件实例eMath调用后,返回的结果也是9,说明事件实例
//eMath返回的也是后注册的函数DecMath的返回值
}
}
}
using System;
using System.Collections.Generic; using System.Text; namespace ClassLibrary { public class AutoCheckClass { public delegate void CheckDelegate(int number); public event CheckDelegate checkEvent; public void WriteInner(int n) { Console.WriteLine(n.ToString()); } public void InitEvent() { checkEvent = WriteInner;//对事件从新赋值 //checkEvent = new CheckDelegate(WriteInner);//也可以用委托对事件进行赋值 } public void Exec(int n) { checkEvent(n); } /* 采用这种方式,public event CheckDelegate checkEvent;会自动生成一个private CheckDelegate checkEvent, 对于public event CheckDelegate checkEvent;的+/-操作都会在编译时反应在private CheckDelegate checkEvent上 而且add/remove .net在编译的时候会自动生成,不用自己再操心,缺点是每个事件的委托都被封装,无法操作其内部的委托 此外采用这种方式定义的事件,可以在定义事件的类的内部直接对事件进行赋值,例如可以在Exec函数中加上下面这句代码: checkEvent = Exec; 表示该事件可以被匹配的函数或委托赋值初始化。 并且对事件进行赋值操作,相当于从新初始化事件内部的委托(同名委托实例),会让赋值之前对事件注册的函数都不再与事件产生关系,具体示例请见本类中InitEvent函数的使用效果。 */ } }
using System;
using System.Collections.Generic; using System.Text; namespace ClassLibrary { public class CheckClass { public delegate void CheckDelegate(int number); private CheckDelegate _checkDelete; public event CheckDelegate checkEvent { add { _checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate; } remove { _checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate; } } public void Exec(int n) { _checkDelete(n); //checkEvent = Exec;注意显示定义事件的方式,不支持对事件直接进行赋值 } /* delegate在编译的时候会被net编译成一个类,如下: public delegate void CheckDelegate(int number);在编译的时候会编译为下面的类 public sealed class CheckDelegate:System.MulticastDelegate { public GreetingDelegate(object @object, IntPtr method); public virtual IAsyncResult BeginInvoke(string name, AsyncCallback callback, object @object); public virtual void EndInvoke(IAsyncResult result); public virtual void Invoke(string name); } 而System.MulticastDelegate继承于System.Delegate,所以下面的代码才会顺利执行 _checkDelete = Delegate.Combine(_checkDelete, value) as CheckDelegate; _checkDelete = Delegate.Remove(_checkDelete, value) as CheckDelegate; 采用这种方法可以让你自己指定事件的委托,甚至可以让多个事件使用同一个委托,且自己实现add/remove,可以实现更复杂的逻辑 此外需要注意的是,采用这种方式定义的事件,就算在定义事件的类的内部都无法对事件直接进行赋值,例如先前在另外种定义方式说到的在Exec函数中加上: checkEvent = Exec; 会报错:事件“ClassLibrary.CheckClass.checkEvent”只能出现在 += 或 -= 的左边 所以在这里我们不应该操作checkEvent,因为它没有同名委托实例,而因该操作_checkDelete */ } }
sing System;
using System.Collections.Generic; using System.Text; using ClassLibrary; namespace DeleGate { class Temp//定义此类是为了在代码中展示函数对委托和事件的另外一种注册方式 { public delegate void TempDelegate(int u); public static TempDelegate td; public static event TempDelegate ed; } class Program { private static void CheckMod(int number) { if (number % 2 == 0) Console.WriteLine("输入的是偶数"); else Console.WriteLine("输入的不是偶数"); } private static void CheckPositive(int number) { if (number > 0) Console.WriteLine("输入的是正数"); else Console.WriteLine("输入的不是正数"); } static void Main(string[] args) { CheckClass cc = new CheckClass(); cc.checkEvent += new CheckClass.CheckDelegate(CheckMod); cc.checkEvent += new CheckClass.CheckDelegate(CheckPositive); AutoCheckClass acc = new AutoCheckClass(); acc.checkEvent += new AutoCheckClass.CheckDelegate(CheckMod); acc.checkEvent += new AutoCheckClass.CheckDelegate(CheckPositive); //acc.InitEvent();//执行了这个方法后,由于对事件从新赋了值,上面对事件注册的两个函数都会失效 Temp.td = CheckMod;//这表示对委托进行赋值(等同于:Temp.td = new Temp.TempDelegate(CheckMod);),和对事件赋值一样,对委托进行赋值相当于初始化委托,会让赋值之前在委托上注册的函数与委托失去注册关系。 Temp.td += CheckPositive; Console.WriteLine("Temp的结果"); Temp.td(50); Temp.ed += CheckMod; Temp.ed += CheckPositive; Console.WriteLine("cc的结果"); cc.Exec(50); Console.WriteLine("acc的结果"); acc.Exec(50); Console.ReadKey(); } } }
附加更新补充 调用委托实例的对象并不是调用委托函数的对象 通过前面的例子,我们了解到了,委托其实就是C#中的函数指针,有了委托我们可以像使用变量一样来使用函数。但是请切记调用委托实例的对象,绝不是调用委托函数的对象。这一点我们通过如下例子来说明. using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DelagateInstanceCall
{
//DelegateContainer是定义委托类型DelMethod和委托实例delMethod的类
class DelegateContainer
{
public delegate void DelMethod();//定义一个无参数且无返回值的委托类型DelMethod
public DelMethod delMethod;//定义委托类型DelMethod的委托实例delMethod
public int i = 100;//定义一个int类型的变量i在类DelegateContainer之中,赋值100
}
//MethodDemo是定义委托函数DisplayMethod的类
class MethodDemo
{
protected int i = 200;//定义一个int类型的变量i在类MethodDemo之中,赋值200
//定义委托函数DisplayMethod
public void DisplayMethod()
{
Console.WriteLine("Varible i is : " + this.i.ToString());//显示变量i的值,通过这里的值就可以知道委托函数DisplayMethod的调用对象是谁
}
}
class Program
{
static void Main(string[] args)
{
DelegateContainer delCon = new DelegateContainer();//构造类DelegateContainer的对象delCon
MethodDemo metDemo = new MethodDemo();//构造类MethodDemo的对象metDemo
delCon.delMethod += metDemo.DisplayMethod;//将函数DisplayMethod注册到委托实例delMethod,让其作为delMethod的委托函数
delCon.delMethod();//调用委托实例delMethod的时候,就会调用在它上注册的委托函数DisplayMethod,那么在执行委托函数DisplayMethod时,其内部代码中的this,到底指的是
//委托实例delMethod的调用对象delCon呢,还是委托函数DisplayMethod的调用对象metDemo呢?
//我可以看到这里输出的结果是"Varible i is : 200",说明DisplayMethod内部的this指的是委托函数DisplayMethod本身的调用对象metDemo。这里大家很容易搞混淆,由于我们上面是通过
//调用委托实例delCon.delMethod来调用委托函数metDemo.DisplayMethod的,看到delCon.delMethod()时大家潜意识可能就会认为由于调用委托实例delMethod的对象是delCon,就认为
//调用委托实例delMethod上注册函数DisplayMethod的对象也是delCon,其实这是错误的。大家一定要记住委托实例只是一个壳子,它只是用来代表在其上注册的函数,但它并不会改变注册函数
//的环境变量(比如函数的调用对象等),由于我们上面将委托函数DisplayMethod注册到委托实例delMethod时,使用的是delCon.delMethod += metDemo.DisplayMethod,所以函数的调用
//对象始终都是等号右边的对象metDemo,而不会是左边的对象delCon,而调用等号左边的委托实例delCon.delMethod()时,相当于就是在执行等号右边的metDemo.DisplayMethod(),
//所以委托函数DisplayMethod的调用对象始终是metDemo。
//由此请大家一定要记住,调用委托实例的对象和调用委托函数的对象没有丝毫关系,要看委托函数是谁调用的,还得要看函数注册到委托实例时,等号右边注册函数前的调用对象是谁。
Console.ReadKey();
}
}
}
从上面这个例子,我们可以牢牢记住,调用委托实例的对象和调用委托函数的对象没有丝毫关系,要看委托函数是谁调用的,还得要看函数注册到委托实例时,等号右边注册函数前的调用对象是谁。这样在使用委托时就不会出错和弄混淆。
delegate event 基础知识写的比较详细。如果能在之前多写一点为什么要实现delegate?为什么能给程序在使用方法上带来灵活性?等等,就更加的完美了。
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论