c#基础-委托

1.1 概述

定义

  • 委托(delegate)是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 你可以通过委托实例调用方法。

  • 委托用于将方法作为参数传递给其他方法。 事件处理程序就是通过委托调用的方法

  • 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。 该方法可以是静态方法,也可以是实例方法

  • 将方法作为参数进行引用的能力使委托成为定义回调方法的理想选择。 可编写一个比较应用程序中两个对象的方法。 该方法可用在排序算法的委托中。 由于比较代码与库分离,因此排序方法可能更常见

  • Delegate至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型

属性

委托的属性
委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
委托允许将方法作为参数进行传递。
委托可用于定义回调方法。
委托可以链接在一起;例如,可以对一个事件调用多个方法。
方法不必与委托类型完全匹配。 有关详细信息,请参阅使用委托中的变体。
使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。 若要详细了解 lambda 表达式,请参阅 lambda 表达式

1.2 委托的使用

委托是安全封装方法的类型,类似于 C 和 C++ 中的函数指针。 与 C 函数指针不同的是,委托是面向对象的、类型安全的和可靠的

1.2.1 委托构造

委托对象通常可采用两种方式进行构造

  • 使用方法名称构造委托对象
using System;

public class Program
{
    public delegate void GreetingDelegate(string name);

    public static void Main()
    {
        // 使用方法名称构造委托对象
        GreetingDelegate greetDelegate = Greet;
        greetDelegate("Alice");
    }

    public static void Greet(string name)
    {
        Console.WriteLine("Hello, " + name + "!");
    }
}
  • 使用 Lambda 表达式构造委托对象
using System;

public class Program
{
    public delegate void GreetingDelegate(string name);

    public static void Main()
    {
        // 使用 Lambda 表达式构造委托对象
        GreetingDelegate greetDelegate = (name) => Console.WriteLine("Hello, " + name + "!");
        greetDelegate("Bob");
    }
}
  • 使用匹配签名声明委托类型并声明方法
// Declare a delegate.
delegate void NotifyCallback(string str);
static void Notify(string name)
{
    Console.WriteLine($"Notification received for: {name}");
}
NotifyCallback del1 = new NotifyCallback(Notify);
  • 声明匿名方法
NotifyCallback del2 = delegate(string name)
    { Console.WriteLine($"Notification received for: {name}"); };

1.2.2 查询委托

委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,不能从其派生出自定义类,以在委托上调用该类定义的方法和属性。 例如,查询委托调用列表中方法的数量

int invocationCount = delegateFuncName.GetInvocationList().GetLength(0);

1.2.3 映射到静态方法和实例方法

// Declare a delegate
delegate void Callback();

class SampleClass
{
    public void InstanceMethod()
    {
        Console.WriteLine("A message from the instance method.");
    }

    static public void StaticMethod()
    {
        Console.WriteLine("A message from the static method.");
    }
}

class TestSampleClass
{
    static void Main()
    {
        var sc = new SampleClass();

        // Map the delegate to the instance method:
        Callback d = sc.InstanceMethod;
        d();

        // Map to the static method:
        d = SampleClass.StaticMethod;
        d();
    }
}
/* Output:
    A message from the instance method.
    A message from the static method.
*/

1.3 委托的多播

委托的多播是指一个委托对象可以封装多个方法,当调用委托时,它会依次调用封装的多个方法。多播委托允许将多个方法链接在一起,以便在调用委托时依次触发它们,仅可合并类型相同的委托。

在 C# 中,多播委托使用 + 和 - 运算符来添加和移除方法。添加方法使用 += 运算符,移除方法使用 -= 运算符

using System;

public delegate void MathOperationDelegate(int x, int y);

public class Program
{
    public static void Main()
    {
        MathOperationDelegate mathDelegate = Add;
        mathDelegate += Subtract;
        mathDelegate += Multiply;

        int a = 10, b = 5;
        mathDelegate(a, b);

        mathDelegate -= Subtract;
        mathDelegate(a, b);
    }

    public static void Add(int x, int y)
    {
        Console.WriteLine("Addition result: " + (x + y));
    }

    public static void Subtract(int x, int y)
    {
        Console.WriteLine("Subtraction result: " + (x - y));
    }

    public static void Multiply(int x, int y)
    {
        Console.WriteLine("Multiplication result: " + (x * y));
    }
}

output

Addition result: 15
Subtraction result: 5
Multiplication result: 50
Addition result: 15
Multiplication result: 50

1.4 泛型委托

泛型:可以根据要处理的精确数据类型定制方法、类、结构或接口。 例如,不使用允许键和值为任意类型的 Hashtable 类,而使用 Dictionary<TKey,TValue> 泛型类并指定允许的密钥和值类型。 泛型的优点包括:代码的可重用性增加,类型安全性提高

泛型委托:不需要定义委托性质,可直接定义委托

public class Generic<T>
{
    public T Field;
}
  1. 协变
    • 协变允许将一个派生类型(子类)转换为一个基类型(父类)。
    • 在泛型接口和委托中,如果泛型类型参数声明了 out 修饰符,那么这个类型参数就支持协变。
    • 协变的类型参数可以用作方法的返回类型。
    • 示例:将 IEnumerable<Derived> 赋值给 IEnumerable<Base>
  2. 逆变
    • 逆变允许将一个基类型(父类)转换为一个派生类型(子类)。
    • 在泛型委托中,如果泛型类型参数声明了 in 修饰符,那么这个类型参数就支持逆变。
    • 逆变的类型参数可以用作方法的参数类型。
    • 示例:将 Action<Base> 赋值给 Action<Derived>
// 定义一个简单的接口
interface IAnimal
{
    string Sound();
}
// 实现派生接口
class Dog : IAnimal
{
    public string Sound() => "Woof!";
}

class Cat : IAnimal
{
    public string Sound() => "Meow!";
}

// 使用协变和逆变
delegate TResult MyDelegate<in T, out TResult>(T arg);

public class Program
{
    public static void Main()
    {
        // 协变例子
        MyDelegate<IAnimal, string> animalSound = (animal) => animal.Sound();
        IAnimal dog = new Dog();
        IAnimal cat = new Cat();
        Console.WriteLine(animalSound(dog));
        Console.WriteLine(animalSound(cat));

        // 逆变例子
        MyDelegate<Dog, string> dogSound = (dog) => dog.Sound();
        Dog myDog = new Dog();
        Console.WriteLine(dogSound(myDog)); 
    }
}
/*
协变例子输出:
Woof!
Meow!
逆变例子输出:
Woof!
*/

1.4.1 Func

使用协变类型参数的委托可以让派生类委托转为基类委托

  • Func必须要有返回值,最多可以有16个参数
public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Drawing a shape");
    }
}
public class Circle : Shape
{
    public new void Draw()
    {
        Console.WriteLine("Drawing a circle");
    }
}
public class Program
{
    public static Circle CreateCircle()
    {
        return new Circle();
    }
    public static void Main()
    {
        // 派生类委托
        Func< Circle > circleFactory = CreateCircle;
        // 基类委托
        Func< Shape > shapeFactory = CreateCircle;
        var circle = circleFactory();
        circle.Draw();

        var shape = shapeFactory();
        shape.Draw();
    }
}

output

Drawing a circle
Drawing a shape

1.4.2 Action

使用逆变类型参数的委托可以让基类委托转为派生类委托

  • Action没有返回值,最多可以有16个参数
public class Person { }
public class Employee : Person { }
class Program
{
    static void AddToContacts(Person person)
    {
        // 此方法添加一个Person对象到联系人列表。
    }

    static void Main()
    {
        // 在不使用变量的情况下创建委托的实例  
        Action<Person> addPersonToContacts = AddToContacts;

        /*
         *  Action代表期望具有Employee参数的方法,
            但是可以为它分配一个具有Person参数的方法
            因为Employee派生自Person
         */
        Action<Employee> addEmployeeToContacts = AddToContacts;

    }
}

1.4.3 Func 和 Action区别

特点 Func Action
参数数量 0 到 16 个 0 到 16 个
返回值 有返回值(泛型类型) 无返回值
委托类型示例 Func Action
示例用途 计算、操作等有返回值的方法 打印、通知等无返回值的方法

1.5 Predicate

一种专门的委托类型 Predicate,此类型返回单个值的测试结果

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

对于任何 Predicate 类型,均存在一个在结构上等效的 Func 类型,例如:

Func<string, bool> TestForString;
Predicate<string> AnotherTestForString;

1.6 委托与事件

1.6.1 事件介绍

  • 使用+=-=进行订阅和取消订阅
  • 事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。
  • C# 中使用事件机制实现线程间的通信
  • 事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
  • 发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
  • 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)
  • 类之外的代码无法引发事件,也不能执行任何其它操作。

1.6.2 事件调用

  1. 定义使用 event 关键字的事件,该事件(在此示例中,为 EventHandler<FileListArgs>)的类型必须为委托类型:

    public event EventHandler<FileListArgs> Progress;
    
  2. 想要引发事件时,使用委托调用语法调用事件处理程序:

    Progress?.Invoke(this, new FileListArgs(file));
    
  3. 处理程序方法通常为前缀“On”,后跟事件名称,如下所示:

    EventHandler<FileListArgs> onProgress = (sender, eventArgs) =>
        Console.WriteLine(eventArgs.FoundFile);
    
    fileLister.Progress += onProgress;
    
  4. .net事件委托的标准签名

    void EventRaised(object sender, EventArgs args);
    

1.6.3 实例

using System;
namespace SimpleEvent
{
  using System;
  /***********发布器类***********/
  public class EventTest
  {
    private int value;

    public delegate void NumManipulationHandler();


    public event NumManipulationHandler ChangeNum;
    protected virtual void OnNumChanged()
    {
      if ( ChangeNum != null )
      {
        ChangeNum(); /* 事件被触发 */
      }else {
        Console.WriteLine( "event has not subscriber" );
        Console.ReadKey(); /* 回车继续 */
      }
    }


    public EventTest()
    {
      int n = 5;
      SetValue( n );
    }


    public void SetValue( int n )
    {
      if ( value != n )
      {
        value = n;
        OnNumChanged();
      }
    }
  }


  /***********订阅器类***********/

  public class subscribEvent
  {
    public void printf()
    {
      Console.WriteLine( "subscriber1 action" );
      Console.ReadKey(); /* 回车继续 */
    }
  }

  /***********触发***********/
  public class MainClass
  {
    public static void Main()
    {
      EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
      subscribEvent v = new subscribEvent(); /* 实例化对象 */
      e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
      e.SetValue( 7 );
      e.SetValue( 11 );
    }
  }
}

​ output

event has not subscriber
subscriber1 action
subscriber1 action
  1. 一个简单的用于热水锅炉系统故障排除的应用程序。当维修工程师检查锅炉时,锅炉的温度和压力会随着维修工程师的备注自动记录到日志文件中
using System;
using System.IO;

namespace BoilerEventAppl
{

   // boiler 类
   class Boiler
   {
      private int temp;
      private int pressure;
      public Boiler(int t, int p)
      {
         temp = t;
         pressure = p;
      }

      public int getTemp()
      {
         return temp;
      }
      public int getPressure()
      {
         return pressure;
      }
   }
   // 事件发布器
   class DelegateBoilerEvent
   {
      public delegate void BoilerLogHandler(string status);

      // 基于上面的委托定义事件
      public event BoilerLogHandler BoilerEventLog;

      public void LogProcess()
      {
         string remarks = "O. K";
         Boiler b = new Boiler(100, 12);
         int t = b.getTemp();
         int p = b.getPressure();
         if(t > 150 || t < 80 || p < 12 || p > 15)
         {
            remarks = "Need Maintenance";
         }
         OnBoilerEventLog("Logging Info:\n");
         OnBoilerEventLog("Temparature " + t + "\nPressure: " + p);
         OnBoilerEventLog("\nMessage: " + remarks);
      }

      protected void OnBoilerEventLog(string message)
      {
         if (BoilerEventLog != null)
         {
            BoilerEventLog(message);
         }
      }
   }
   // 该类保留写入日志文件的条款
   class BoilerInfoLogger
   {
      FileStream fs;
      StreamWriter sw;
      public BoilerInfoLogger(string filename)
      {
         fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
         sw = new StreamWriter(fs);
      }
      public void Logger(string info)
      {
         sw.WriteLine(info);
      }
      public void Close()
      {
         sw.Close();
         fs.Close();
      }
   }
   // 事件订阅器
   public class RecordBoilerInfo
   {
      static void Logger(string info)
      {
         Console.WriteLine(info);
      }//end of Logger

      static void Main(string[] args)
      {
         BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt");
         DelegateBoilerEvent boilerEvent = new DelegateBoilerEvent();
         boilerEvent.BoilerEventLog += new 
         DelegateBoilerEvent.BoilerLogHandler(Logger);
         boilerEvent.BoilerEventLog += new 
         DelegateBoilerEvent.BoilerLogHandler(filelog.Logger);
         boilerEvent.LogProcess();
         Console.ReadLine();
         filelog.Close();
      }//end of main

   }//end of RecordBoilerInfo
}

​ output

Logging info:

Temperature 100
Pressure 12

Message: O. K
  1. 事件委托签名查找文件并打印
public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }

    public FileFoundArgs(string fileName) => FoundFile = fileName;
  
}
public class FileSearcher
{
    public event EventHandler<FileFoundArgs>? FileFound;

    public void Search(string directory, string searchPattern)
    {
        foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
        {
            RaiseFileFound(file);
        }
    }

    private void RaiseFileFound(string file) =>
        FileFound?.Invoke(this, new FileFoundArgs(file));

}

class Program
{
    static void Main()
    {
        FileSearcher searcher = new FileSearcher();
        searcher.FileFound += HandleFileFound; // 订阅 FileFound 事件

        // 执行搜索
        searcher.Search("C:\\Users\\fujia.huang\\Desktop", "*.vpp");
    }

    static void HandleFileFound(object sender, FileFoundArgs e)
    {
        Console.WriteLine("File found: " + e.FoundFile);
    }
}


	4. 定义并引发类似字段的事件
public class FileFoundArgs : EventArgs
{
    public string FoundFile { get; }

    public FileFoundArgs(string fileName) => FoundFile = fileName;
}

public class FileSearcher
{
    public event EventHandler<FileFoundArgs>? FileFound;

    public void Search(string directory, string searchPattern)
    {
        foreach (var file in Directory.EnumerateFiles(directory, searchPattern))
        {
            RaiseFileFound(file);
        }
    }

    private void RaiseFileFound(string file) =>
        FileFound?.Invoke(this, new FileFoundArgs(file));

}

class test
{
    public event EventHandler<FileFoundArgs>? FileFound;
    static int filesFound = 0;

    EventHandler<FileFoundArgs> onFileFound = (sender, eventArgs) =>
    {
        Console.WriteLine(eventArgs.FoundFile);
        filesFound++;
    };

    static void HandleFileFound(object sender, FileFoundArgs e)
    {
        Console.WriteLine("File found: " + e.FoundFile);
    }

    public void testt()
    {
        var fileLister = new FileSearcher();
        Console.WriteLine($"filesFound = {filesFound}\n");
        fileLister.FileFound += onFileFound;
       // fileLister.FileFound += HandleFileFound;
        fileLister.Search("C:\\Users\\fujia.huang\\Desktop", "*.vpp");
        Console.WriteLine($"filesFound = {filesFound}\n");
    }
}
class Program
{

    static void Main(string[] args)
    {
        var t = new test();
        t.testt();
        
    }
}
  1. 异步事件订阅
worker.StartWorking += async (sender, eventArgs) =>
{
    try
    {
        await DoWorkAsync();
        //await 表示异步等待 DoWorkAsync() 方法的完成,同时允许主线程在等待期间不被阻塞
    }
    catch (Exception e)
    {
        //Some form of logging.
        Console.WriteLine($"Async task failure: {e.ToString()}");
        // Consider gracefully, and quickly exiting.
    }
};
posted @ 2023-08-23 10:12  InsiApple  阅读(29)  评论(0编辑  收藏  举报