C#中的委托
C# 委托是.NET Framework 使用的一种类型安全的函数指针。 委托通常用于实现回调和事件侦听器。 委托无需了解其使用的方法类的任何知识。
委托是引用类型。 但是委托不是引用对象,而是引用方法。
在以下情况下使用代理:
- 事件处理程序
- 回呼
- LINQ
- 设计模式的实施
委托没有什么可以用常规方法完成的。 之所以使用代表,是因为它们带来了许多优点。 它们提高了应用和代码重用的灵活性。 像接口一样,委托使我们能够解耦和泛化我们的代码。 委托还允许将方法作为参数传递。 当我们需要确定在运行时调用哪种方法时,可以使用委托。 最后,委托人提供了一种无需对子类进行子类化就可以对它的行为进行专门化的方法。 类可能具有复杂的泛型行为,但仍应专门化。 类是通过继承或通过委托来专用的。
C# 使用委托
我们将有一些简单的示例显示如何使用委托。
Program.
using System;
namespace SimpleDelegate
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
var md = new MyDelegate(MyCallback);
md();
}
static void MyCallback()
{
Console.WriteLine("Calling callback");
}
}
}
我们声明一个委托,创建该委托的实例并调用它。
delegate void MyDelegate();
这是我们的委托人声明。 它不返回任何值,不接受任何参数。
var md = new MyDelegate(MyCallback);
我们创建委托的实例。 调用时,委托将调用静态Callback()
方法。
md();
我们打电话给代表。
$ dotnet run Calling callback
这是输出。
我们可以使用不同的语法来创建和使用委托。
Program.
using System;
namespace SimpleDelegate2
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate del = MyCallback;
del();
}
static void MyCallback()
{
Console.WriteLine("Calling callback");
}
}
}
创建委托的实例时,我们可以保存一些类型。
MyDelegate del = MyCallback;
这是创建委托的另一种方法。 我们直接指向方法名称。
C# 委托指向不同的方法
委托可以随着时间指向不同的方法。
Program.
using System;
namespace DifferentMethods
{
public delegate void NameDelegate(string msg);
public class Person
{
public string firstName;
public string secondName;
public Person(string firstName, string secondName)
{
this.firstName = firstName;
this.secondName = secondName;
}
public void ShowFirstName(string msg)
{
Console.WriteLine(msg + this.firstName);
}
public void ShowSecondName(string msg)
{
Console.WriteLine(msg + this.secondName);
}
}
class Program
{
public static void Main()
{
var per = new Person("Fabius", "Maximus");
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
nDelegate = new NameDelegate(per.ShowSecondName);
nDelegate("Call 2: ");
}
}
}
在此示例中,我们只有一名代表。 该委托用于指向Person
类的两个方法。 方法与委托一起调用。
public delegate void NameDelegate(string msg);
使用delegate
关键字创建委托。 委托签名必须与委托调用的方法的签名匹配。
1
2
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
我们创建一个新委托的实例,该实例指向ShowFirstName()
方法。 稍后我们通过委托调用该方法。
$ dotnet run
Call 1: Fabius
Call 2: Maximus
这两个名称都是通过代理打印的。
C# 多播委托
多播委托是一个拥有对多个方法的引用的委托。 多播委托必须仅包含返回 void 的方法,否则将存在运行时异常。
Program.
using System;
namespace MulticastDelegate
{
delegate void MyDelegate(int x, int y);
public class Oper
{
public static void Add(int x, int y)
{
Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
}
public static void Sub(int x, int y)
{
Console.WriteLine("{0} - {1} = {2}", x, y, x - y);
}
}
class Program
{
static void Main()
{
var del = new MyDelegate(Oper.Add);
del += new MyDelegate(Oper.Sub);
del(6, 4);
del -= new MyDelegate(Oper.Sub);
del(2, 8);
}
}
}
这是一个多播委托的示例。
delegate void MyDelegate(int x, int y);
我们的代表接受两个参数。 我们有一个Oper
类,它具有两个静态方法。 一个将两个值相加,另一个将两个值相减。
var del = new MyDelegate(Oper.Add);
我们创建委托的实例。 委托指向Oper
类的静态Add()
方法。
1
2
del += new MyDelegate(Oper.Sub);
del(6, 4);
我们将另一个方法插入到现有的委托实例中。 委托的第一次调用将调用两个方法。
1
2
del -= new MyDelegate(Oper.Sub);
del(2, 8);
我们从委托中删除一种方法。 委托的第二次调用仅调用一种方法。
$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10
这是程序的输出。
C# 匿名方法
可以对委托使用匿名方法。
Program.
using System;
namespace Anonymous
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
del();
}
}
}
当将匿名方法与委托一起使用时,我们可以省略方法声明。 该方法没有名称,只能通过委托来调用。
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
在这里,我们创建一个指向匿名方法的委托。 匿名方法的主体用{}
字符括起来,但是没有名称。
C# 委托作为方法参数
委托可以用作方法参数。
Program.
using System;
namespace MethodParameters
{
delegate int Arithm(int x, int y);
class Program
{
static void Main()
{
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
}
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
static int Multiply(int x, int y)
{
return x * y;
}
static int Divide(int x, int y)
{
return x / y;
}
}
}
我们有一个DoOperation()
方法,该方法将一个委托作为参数。
delegate int Arithm(int x, int y);
这是一个委托声明。
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
这是DoOperation()
方法的实现。 第三个参数是委托。 DoOperation()
方法调用一个方法,该方法作为第三个参数传递给它。
1
2
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
我们称为DoOperation()
方法。 我们传递两个值和一个方法给它。 我们对这两个值的处理方式取决于我们通过的方法。 这就是使用委托所带来的灵活性。
$ dotnet run
20
5
C# 事件
事件是由某些操作触发的消息。 单击按钮或滴答滴答即是这种动作。 触发事件的对象称为发送者,而接收事件的对象称为接收者。
按照约定,.NET Framework 中的事件委托具有两个参数:引发事件的源和事件的数据。
Program.
using System;
namespace SimpleEvent
{
public delegate void OnFiveHandler(object sender, EventArgs e);
class FEvent
{
public event OnFiveHandler FiveEvent;
public void OnFiveEvent()
{
if (FiveEvent != null)
{
FiveEvent(this, EventArgs.Empty);
}
}
}
class Program
{
static void Main()
{
var fe = new FEvent();
fe.FiveEvent += new OnFiveHandler(Callback);
var random = new Random();
for (int i = 0; i < 10; i++)
{
int rn = random.Next(6);
Console.WriteLine(rn);
if (rn == 5)
{
fe.OnFiveEvent();
}
}
}
public static void Callback(object sender, EventArgs e)
{
Console.WriteLine("Five Event occurred");
}
}
}
我们有一个简单的示例,可以在其中创建和启动事件。 生成一个随机数。 如果数字等于 5,则会生成FiveEvent
事件。
public event OnFiveHandler FiveEvent;
使用event
关键字声明事件。
fe.FiveEvent += new OnFiveHandler(Callback);
在这里,我们将名为FiveEvent
的事件插入到Callback()
方法中。 换句话说,如果触发了ValueFive
事件,则将执行Callback()
方法。
public void OnFiveEvent()
{
if(FiveEvent != null)
{
FiveEvent(this, EventArgs.Empty);
}
}
当随机数等于 5 时,我们调用OnFiveEvent()
方法。 在这种方法中,我们引发了FiveEvent
事件。 此事件不包含任何参数。
$ dotnet run
1
1
5
Five Event occurred
Five Event occurred
这是一个示例输出。
C# 复杂事件示例
接下来,我们有一个更复杂的示例。 这次,我们将通过生成的事件发送一些数据。
Program.
using System;
namespace ComplexEvent
{
public delegate void OnFiveHandler(object sender, FiveEventArgs e);
public class FiveEventArgs : EventArgs
{
public int count;
public DateTime time;
public FiveEventArgs(int count, DateTime time)
{
this.count = count;
this.time = time;
}
}
public class FEvent
{
public event OnFiveHandler FiveEvent;
public void OnFiveEvent(FiveEventArgs e)
{
FiveEvent(this, e);
}
}
public class RandomEventGenerator
{
public void Generate()
{
int count = 0;
FiveEventArgs args;
var fe = new FEvent();
fe.FiveEvent += new OnFiveHandler(Callback);
var random = new Random();
for (int i = 0; i < 10; i++)
{
int rn = random.Next(6);
Console.WriteLine(rn);
if (rn == 5)
{
count++;
args = new FiveEventArgs(count, DateTime.Now);
fe.OnFiveEvent(args);
}
}
}
public void Callback(object sender, FiveEventArgs e)
{
Console.WriteLine("Five event {0} occurred at {1}",
e.count, e.time);
}
}
class Program
{
static void Main()
{
var reg = new RandomEventGenerator();
reg.Generate();
}
}
}
我们有四个班。 FiveEventArgs
带有事件对象的一些数据。 FEvent
类封装了事件对象。 RandomEventGenerator
类负责生成随机数。 它是事件发送者。 最后,ComplexEvent
是主要的应用类。
1
2
3
4
5
public class FiveEventArgs : EventArgs
{
public int count;
public DateTime time;
...
FiveEventArgs
在事件对象内部传送数据。 它继承自EventArgs
基类。 计数和时间成员是将被初始化并随事件一起携带的数据。
if (rn == 5)
{
count++;
args = new FiveEventArgs(count, DateTime.Now);
fe.OnFiveEvent(args);
}
如果生成的随机数等于 5,我们用当前计数和DateTime
值实例化FiveEventArgs
类。 count
变量对生成此事件的次数进行计数。 DateTime
值保存事件生成的时间。
$ dotnet run
Five event 1 occurred at 10/22/2019 2:13:10 PM
3
0
这是程序的示例输出。
C# 预定义的委托
.NET 框架具有多个内置的委托,这些委托减少了所需的输入并简化了开发人员的编程工作。
C# 动作委托
操作委托封装了没有参数且不返回值的方法。
Program.
using System;
namespace ActionDelegate
{
class Program
{
static void Main()
{
Action act = ShowMessage;
act();
}
static void ShowMessage()
{
Console.WriteLine("C# language");
}
}
}
使用预定义的委托可以进一步简化编程。 我们不需要声明委托类型。
Action act = ShowMessage; act();
我们实例化一个动作委托。 委托指向ShowMessage()
方法。 调用委托时,将执行ShowMessage()
方法。
有多种类型的动作委托。 例如,Action<T>
委托封装了一个采用单个参数且不返回值的方法。
Program.
using System;
namespace ActionDelegate2
{
class Program
{
static void Main()
{
Action<string> act = ShowMessage;
act("C# language");
}
static void ShowMessage(string message)
{
Console.WriteLine(message);
}
}
}
我们修改前面的示例,以使用带有一个参数的动作委托。
1
2
Action<string> act = ShowMessage;
act("C# language");
我们创建动作< T >委托的实例,并使用一个参数对其进行调用。
C# 谓词委托
谓词是一种返回 true 或 false 的方法。 谓词委托是对谓词的引用。 谓词对于过滤值列表非常有用。
Program.
using System;
using System.Collections.Generic;
namespace PredicateDelegate
{
class Program
{
static void Main()
{
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };
Predicate<int> myPred = greaterThanThree;
List<int> vals2 = vals.FindAll(myPred);
foreach (int i in vals2)
{
Console.WriteLine(i);
}
}
static bool greaterThanThree(int x)
{
return x > 3;
}
}
}
我们有一个整数值列表。 我们要过滤所有大于三的数字。 为此,我们使用谓词委托。
List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };
这是整数值的一般列表。
Predicate<int> myPred = greaterThanThree;
我们创建一个谓词委托的实例。 委托指向谓词,这是一种返回 true 或 false 的特殊方法。
List<int> vals2 = vals.FindAll(myPred);
FindAll()
方法检索与指定谓词定义的条件匹配的所有元素。
static bool greaterThanThree(int x)
{
return x > 3;
}
对于大于三个的所有值,谓词返回 true。
C# 预定义的委托
.NET 框架具有多个内置的委托,这些委托减少了所需的输入并简化了开发人员的编程工作。
C# Action委托
Action 是.NET Framework内置的泛型委托,可以使用Action 委托以参数形式传递方法,而不用显示声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能有返回值。
查看Action的定义:
using System.Runtime.CompilerServices;
namespace System
{
//
// 摘要:
// 封装一个方法,该方法不具有参数且不返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate void Action();
}
你会发现,Action其实就是没有返回值的delegate。
示例
Action委托至少0个参数,至多16个参数,无返回值。
Action 表示无参,无返回值的委托。
Action<int,string> 表示有传入参数int,string无返回值的委托。
Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托。
Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托。
代码示例如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ActionDemo
{
class Program
{
static void Main(string[] args)
{
// 无参数无返回值的委托
Action action1 = new Action(ActionWithNoParaNoReturn);
action1();
Console.WriteLine("----------------------------");
// 使用delegate
Action action2 = delegate { Console.WriteLine("这里是使用delegate"); };
// 执行
action2();
Console.WriteLine("----------------------------");
// 使用匿名委托
Action action3 = () => { Console.WriteLine("这里是匿名委托"); };
action3();
Console.WriteLine("----------------------------");
// 有参数无返回值的委托
Action<int> action4 = new Action<int>(ActionWithPara);
action4(23);
Console.WriteLine("----------------------------");
// 使用delegate
Action<int> action5 = delegate (int i) { Console.WriteLine($"这里是使用delegate的委托,参数值是:{i}"); };
action5(45);
Console.WriteLine("----------------------------");
// 使用匿名委托
Action<string> action6 = (string s) => { Console.WriteLine($"这里是使用匿名委托,参数值是:{s}"); };
action6("345");
Console.WriteLine("----------------------------");
// 多个参数无返回值的委托
Action<int, string> action7 = new Action<int, string>(ActionWithMulitPara);
action7(7, "abc");
Console.WriteLine("----------------------------");
// 使用delegate
Action<int, int, string> action8 = delegate (int i1, int i2, string s)
{
Console.WriteLine($"这里是三个参数的Action委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s}");
};
action8(12, 34, "abc");
Console.WriteLine("----------------------------");
Action<int,int,string, string> action9 = (int i1,int i2, string s1,string s2) =>
{
Console.WriteLine($"这里是使用四个参数的委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s1},参数4的值是:{s2}");
};
// 执行委托
action9(34,56, "abc","def");
Console.ReadKey();
}
static void ActionWithNoParaNoReturn()
{
Console.WriteLine("这是无参数无返回值的Action委托");
}
static void ActionWithPara(int i)
{
Console.WriteLine($"这里是有参数无返回值的委托,参数值是:{i}");
}
static void ActionWithMulitPara(int i,string s)
{
Console.WriteLine($"这里是有两个参数无返回值的委托,参数1的值是:{i},参数2的值是:{s}");
}
}
}
Func委托
Func委托代表有返回类型的委托
查看Func的定义:
using System.Runtime.CompilerServices;
namespace System
{
//
// 摘要:
// 封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值。
//
// 参数:
// arg1:
// 此委托封装的方法的第一个参数。
//
// arg2:
// 此委托封装的方法的第二个参数。
//
// 类型参数:
// T1:
// 此委托封装的方法的第一个参数的类型。
//
// T2:
// 此委托封装的方法的第二个参数的类型。
//
// TResult:
// 此委托封装的方法的返回值类型。
//
// 返回结果:
// 此委托封装的方法的返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
}
你会发现,Func其实就是有多个输出参数并且有返回值的delegate。
Func至少0个输入参数,至多16个输入参数,根据返回值泛型返回。必须有返回值,不可void。
Func 表示没有输入参参,返回值为int类型的委托。
Func<object,string,int> 表示传入参数为object, string ,返回值为int类型的委托。
Func<object,string,int> 表示传入参数为object, string, 返回值为int类型的委托。
Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型),返回值为int类型的委托。
实例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FunDemo
{
class Program
{
static void Main(string[] args)
{
// 无参数,只要返回值
Func<int> fun1 = new Func<int>(FunWithNoPara);
int result1= fun1();
Console.WriteLine(result1);
Console.WriteLine("----------------------------");
Func<int> fun2 = delegate { return 19; };
int result2 = fun2();
Console.WriteLine(result2);
Console.WriteLine("----------------------------");
Func<int> fun3 = () => { return 3; };
int result3 = fun3();
Console.WriteLine(result3);
Console.WriteLine("----------------------------");
//有一个参数,一个返回值
Func<int, int> fun4 = new Func<int, int>(FunWithPara);
int result4 = fun4(4);
Console.WriteLine($"这里是一个参数一个返回值的方法,返回值是:{result4}");
Console.WriteLine("----------------------------");
// 使用委托
Func<int, string> fun5 = delegate (int i) { return i.ToString(); };
string result5 = fun5(5);
Console.WriteLine($"这里是一个参数一个返回值的委托,返回值是:{result5}");
Console.WriteLine("----------------------------");
// 使用匿名委托
Func<int, string> fun6 = (int i) =>
{
return i.ToString();
};
string result6 = fun6(6);
Console.WriteLine($"这里是一个参数一个返回值的匿名委托,返回值是:{result6}");
Console.WriteLine("----------------------------");
// 多个输入参数
Func<int, string, bool> fun7 = new Func<int, string, bool>(FunWithMultiPara);
bool result7 = fun7(2, "2");
Console.WriteLine($"这里是有多个输入参数的方法,返回值是:{result7}");
Console.WriteLine("----------------------------");
// 使用委托
Func<int, string, bool> fun8 = delegate (int i, string s)
{
return i.ToString().Equals(s) ? true : false;
};
bool result8 = fun8(2, "abc");
Console.WriteLine($"这里是有多个输入参数的委托,返回值是:{result8}");
Console.WriteLine("----------------------------");
// 使用匿名委托
Func<int, string, bool> fun9 = (int i, string s) =>
{
return i.ToString().Equals(s) ? true : false;
};
bool result9 = fun9(45, "ert");
Console.WriteLine($"这里是有多个输入参数的匿名委托,返回值是:{result9}");
Console.ReadKey();
}
static int FunWithNoPara()
{
return 10;
}
static int FunWithPara(int i)
{
return i;
}
static bool FunWithMultiPara(int i,string s)
{
return i.ToString().Equals(s) ? true : false;
}
}
}