C# 委托

devtools/2024/12/22 20:25:52/

文章目录

  • 1. Delegate 委托
  • 2. Func & Action 委托
    • 对Func和Action泛型委托使用变体
    • Func 与 Action 中异常处理
  • 3. 其他委托:Predicate & Comparison & Converter

1. Delegate 委托

在C#中,委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用,即委托就是一种用来指向一个方法的类型变量,可以通过委托实例调用方法,关键字是 delegate。

// 声明一个委托
public delegate void MyDelegate(string message);public class Program
{static void Main(string[] args){// 创建委托实例,绑定到具体的方法MyDelegate myDelegate = new MyDelegate(DisplayMessage);// 使用委托myDelegate("Hello, World!");}// 与委托具有相同签名的方法static void DisplayMessage(string message){Console.WriteLine(message);}
}

现在我们可以更简单的方式使用委托

public class Program
{static void Main(string[] args){MyDelegate myDelegate = message => Console.WriteLine(message);myDelegate("Hello, World!");}
}

委托 vs 接口

委托和接口是不是感觉很类似?都是分离类型的声明和实现,且都可以由不了解实现该接口或委托的类对象使用,那么这两个使用场景有什么区别呢?

C# 官方给出的建议, 以下情况请使用委托:(后面 事件模式/委托模式 举例说明)

  1. 当使用事件设计模式时
  2. 当封装静态方法可取时
  3. 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时
  4. 需要方便的组合
  5. 当类可能需要该方法的多个实现时

以下情况请使用接口:

  1. 当存在一组可能被调用的相关方法时
  2. 当类只需要方法的单个实现时
  3. 当使用接口的类想要将该接口强制转换为其它接口或类类型时
  4. 当正在实现的方法链接到类的类型或标识时,例如比较方法

事件模式 / 委托模式

也可能翻译有偏差,并没有搜索到事件设计模式(eventing design pattern)。我们说的委托模式,是否可以认为是这里指的事件设计模式?
委托模式解耦了委托者与被委托者,同时又需要委托者与被委托者协同完成同一个事件。
在这里插入图片描述

如下代码示例,#1 事件模式时委托者即事件发布类(publisher),被委托者即订阅类(subscriber),同时由于**#5 订阅类的多样性**,对于同一事件会有不同的订阅处理,且**#3 委托者只需要发布事件,而不需要关心订阅者任何其他细节**:

interface ISubscriber
{// 事件处理程序public void HandleSimpleEvent(object sender, EventArgs e);
}public abstract class BaseSubscriber : ISubscriber
{public void Subscribe(SimplePublisher publisher){publisher.SimpleEvent += HandleSimpleEvent;}public abstract void HandleSimpleEvent(object sender, EventArgs e);
}// 订阅事件类1
public class SimpleSubscriber : BaseSubscriber
{public override void HandleSimpleEvent(object sender, EventArgs e){Console.WriteLine("SimpleEvent is raised.");}
}// 订阅事件类2
public class ComplexSubscriber : BaseSubscriber
{public override void HandleSimpleEvent(object sender, EventArgs e){Console.WriteLine("Do complex work in ComplexSubscriber.");Console.WriteLine("ComplexEvent is raised.");}
}// 使用示例
public class Program
{public static void Main(){var subscribers = GetAllSubscribers();if (!subscribers.Any()){Console.WriteLine("No subscriber found!");return;}SimplePublisher publisher = new SimplePublisher();foreach (var subscriber in subscribers){subscriber.Subscribe(publisher);// 触发事件publisher.RaiseEvent();}}// 通过反射获取所有订阅者private static List<BaseSubscriber> GetAllSubscribers(){var pluginTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && !t.IsAbstract && typeof(BaseSubscriber).IsAssignableFrom(t));List<BaseSubscriber> subscribers = new List<BaseSubscriber>();foreach (var pluginType in pluginTypes){var instance = Activator.CreateInstance(pluginType) as BaseSubscriber;if (instance != null){subscribers.Add(instance);}}return subscribers;}}
}------------------Output-----------------------
Do complex work in ComplexSubscriber.
ComplexEvent is raised.
SimpleEvent is raised.

委托组合

c# 允许我们用 + 将多个委托组合成一个新的委托,组合的委托可以调用组成它的所有委托,但是注意,只有相同类型的委托才可以组合。运算符 - 可以从组合的委托中移除某个委托

delegate void Del(string s);class TestClass
{static void Hello(string s){System.Console.WriteLine("  Hello, {0}!", s);}static void Goodbye(string s){System.Console.WriteLine("  Goodbye, {0}!", s);}static void Main(){Del a, b, c, d;// Create the delegate object a that references // the method Hello:a = Hello;// Create the delegate object b that references // the method Goodbye:b = Goodbye;// The two delegates, a and b, are composed to form c: c = a + b;// Remove a from the composed delegate, leaving d, // which calls only the method Goodbye:d = c - a;System.Console.WriteLine("Invoking delegate a:");a("A");System.Console.WriteLine("Invoking delegate b:");b("B");System.Console.WriteLine("Invoking delegate c:");c("C");System.Console.WriteLine("Invoking delegate d:");d("D");}
}
/* Output:
Invoking delegate a:Hello, A!
Invoking delegate b:Goodbye, B!
Invoking delegate c:Hello, C!Goodbye, C!
Invoking delegate d:Goodbye, D!
*/

委托中的协变和逆变

协变:下面官方示例种 Dogs 集成 Mammals,所以 DogsHandler 可以分配给 HandlerMethod 委托

class Mammals {}  
class Dogs : Mammals {}  class Program  
{  // Define the delegate.  public delegate Mammals HandlerMethod();  public static Mammals MammalsHandler()  {  return null;  }  public static Dogs DogsHandler()  {  return null;  }  static void Test()  {  HandlerMethod handlerMammals = MammalsHandler;  // Covariance enables this assignment.  HandlerMethod handlerDogs = DogsHandler;  }  
}

逆变:逆变可以使用一个事件处理程序而不是多个单独的处理程序,如下 MultiHandler 中的第二个参数 System.EventArgs 是 KeyEventhandler 和 MouseEventHandler 的基类,因此这两个委托都可以作为MultiHandler 入参。

public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e);// Event handler that accepts a parameter of the EventArgs type.  
private void MultiHandler(object sender, System.EventArgs e)  
{  label1.Text = System.DateTime.Now.ToString();  
}  public Form1()  
{  InitializeComponent();  // You can use a method that has an EventArgs parameter,  // although the event expects the KeyEventArgs parameter.  this.button1.KeyDown += this.MultiHandler;  // You can use the same method// for an event that expects the MouseEventArgs parameter.  this.button1.MouseClick += this.MultiHandler;  
}

2. Func & Action 委托

Func 是接受任意数量的参数并且有返回值的委托.

public delegate TResult Func<out TResult>();// Func 的 TResult前可以定义任意多个入参 T1, T2 ... ...
public delegate TResult Func<in T,out TResult>(T arg);

Action 是接受任意数量的参数且没有返回值的委托

public delegate void Action<in T>(T obj);

对Func和Action泛型委托使用变体

这里给出官方示例,使用具有协变类型参数的委托:当一个方法的返回值类型 TResult 继承了某个类A,那么就可以将这个方法分配给 Func<in T, out A> / Func 委托

// Simple hierarchy of classes.  
public class Person { }  
public class Employee : Person { }  
class Program  
{  static Employee FindByTitle(String title)  {  return new Employee();  }  static void Test()  {  Func<String, Employee> findEmployee = FindByTitle;  Func<String, Person> findPerson = FindByTitle;  findPerson = findEmployee;  }  
}

使用具有逆变类型参数的委托

public class Person { }  
public class Employee : Person { }  
class Program  
{  static void AddToContacts(Person person)  {  // do something}  static void Test()  {  Action<Person> addPersonToContacts = AddToContacts;  Action<Employee> addEmployeeToContacts = AddToContacts;  addEmployeeToContacts = addPersonToContacts;  }  
}

Func 与 Action 中异常处理

在业务实现的时候,很多场景需要保证数据的原子性,例如带有数据库操作的、一系列动作组成的场景,一旦中间某个动作失败,我们需要将所有状态恢复。

假如这种场景选择使用委托模式,并且在委托方法中抛出异常,由于委托者并不知道被委托者实际的代码流程,即使出现异常,委托者也无法确定要回滚的内容。

如一个简单下单场景:

  1. 用户下订单
  2. 库存有效并预约库存
  3. 创建订单
  4. 调用支付进行支付
  5. 实际扣减库存
  6. 订单生效状态更新

假使抛出了一个非常精确的异常,例如订单在扣减库存阶段失败,并需要委托者处理,那么委托者就必然需要了解被委托者内部实现细节,也就违背了我们使用委托模式的初衷。

所以我们应避免在 Func 和 Action 中抛出异常,或者说对于有原子性要求的业务,应避免使用委托模式。如果场景贴合委托模式,却没有办法保证相关的表达式绝对不抛出异常,那么必须采用其他开销更大的防护措施,即把异常情况页考虑进来。例如:

  1. 拷贝源数据,并在源数据上进行操作,整个委托执行成功时,才会真正覆盖源数据。
  2. 添加异常处理逻辑和额外的数据恢复流程,例如可以设计整个流程能多次重复执行,每个步骤的中间结果都保存下来,通过不同的flag来判断当前流程是否执行成功,如果成功则跳过直接执行下一个步骤,如果不成功则尝试重试。对于始终无法成功的场景,采用其他流程用来恢复数据(可能就需要人工参与)。

3. 其他委托:Predicate & Comparison & Converter

Predicate 是用来判断某个条件是否成立的布尔委托

public delegate bool Predicate<in T>(T obj);

Comparison 是比较两个同类型对象的委托

public delegate int Comparison<in T>(T x, T y);

Converter<TInput, TOutput> 是将对象从一种类型转换为另一种类型的委托

public delegate TOutput Converter<in TInput,out TOutput>(TInput input);

http://www.ppmy.cn/devtools/45349.html

相关文章

林业调查具体是做些什么?

林业调查是对森林资源进行系统的信息收集和处理的过程。 林业调查涵盖了对林木、林地以及林区内生长的动植物及其环境条件的全面评估&#xff0c;旨在及时掌握森林资源的数量、质量和生长消亡的动态规律。这种调查不仅关注森林本身&#xff0c;还包括与之相关的自然环境和经济…

缓冲字符流

BufferedReader/BufferedWriter增加了缓存机制&#xff0c;大大提高了读写文本文件的效率。 字符输入缓冲流 BufferedReader是针对字符输入流的缓冲流对象&#xff0c;提供了更方便的按行读取的方法&#xff1a;readLine();在使用字符流读取文本文件时&#xff0c;我们可以使…

javascript读取本地目录

在JavaScript中&#xff0c;直接读取本地目录的能力受到浏览器安全限制&#xff0c;因为出于隐私和安全考虑&#xff0c;浏览器的JavaScript环境通常不允许直接访问用户的文件系统。然而&#xff0c;随着Web技术的发展&#xff0c;一些现代浏览器引入了File System API或Web Fi…

FL Studio Producer Edition 21.2.3.4004全插件+Crack下载链接(亲测可用,非钓鱼)

FL Studio 21.2.3.4004中文版 中文别名水果编曲软件&#xff0c;是一款全能的音乐制作软件&#xff0c;包括编曲、录音、剪辑和混音等诸多功能&#xff0c;让你的电脑编程一个全能的录音室&#xff0c;它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xf…

C++面试题其一

C和C的区别 C和C都是广泛使用的编程语言&#xff0c;但它们有显著的区别&#xff1a; 语言范式&#xff1a; C&#xff1a;是一种过程化编程语言&#xff0c;强调过程和函数的使用。C&#xff1a;是一种多范式编程语言&#xff0c;支持面向对象编程、泛型编程和过程化编程。 …

Linux DHCP server 配置

参考&#xff1a;linux dhcp配置多vlan ip_linux 接口vlan-CSDN博客 配置静态IP地址&#xff1a; 给固定的MAC地址分配指定的IP地址&#xff0c;固定的IP地址不必包含在指定的IP池中&#xff0c;如果包含在IP地址池中&#xff0c;固定的IP地址会从IP地址池中移除 配置方法&…

很多Oracle中的SQL语句在EF中写不出来

很多复杂的Oracle SQL语句在Entity Framework&#xff08;EF&#xff09;中很难直接表达出来。虽然EF提供了一种方便的方式来使用C#代码查询和操作数据库&#xff0c;但它在处理某些复杂的SQL特性和优化时可能会有局限性。 以下是一些在EF中可能难以直接实现的Oracle SQL功能和…

271 基于matlab的可调Q因子小波变换故障诊断

基于matlab的可调Q因子小波变换故障诊断&#xff0c;可用在轴承、齿轮、活塞等故障诊断中&#xff0c;程序中包含了原始TQWT工具箱和轴承振动信号信号的谱包络的求取。通过仿真数据、实际轴承数据说明了方法的效果。程序已调通&#xff0c;可直接运行。 271 可调Q因子小波变换 …