一、委托 Delegate

1. 什么是委托?

        C# 委托是.NET Framework 使用的一种类型安全的函数指针。

        委托通常用于实现回调和事件侦听器。

        委托是引用类型。 但是委托不是引用对象,而是引用方法。

2. 委托的作用?

        1.  将函数作为函数的的参数进行传递

        2.  声明事件并用来注册

3. 委托的使用

1.委托基本示例

    // 1.声明一个无参无返回值的委托
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            // 2. 创建委托的示例
            var md = new MyDelegate(MyCallback);
            // 3. 调用时,委托将调用静态的MyCallback方法
            md();
        }
        
        // 被委托的方法,这个方法的签名要和委托 一致
        static void MyCallback()
        {
            Console.WriteLine("Calling callback");
        }
    }

声明一个无参无返回值的委托

delegate void MyDelegate();

调用的方法需要和声明的委托的签名一致

var md = new MyDelegate(MyCallback);

我们创建委托的实例。 调用时,委托将调用静态MyCallback()方法。

md();

输出:

$ dotnet run
Calling callback
4. 委托指向不同的方法

       1. 示例:在此示例中, 该委托用于指向Person类的两个方法。 方法与委托一起调用。

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: ");
        }
    }
}

  2. 使用delegate关键字创建委托。 委托签名必须与委托调用的方法的签名一致。

public delegate void NameDelegate(string msg);

3. 创建一个新委托的实例,该实例指向ShowFirstName()方法。 通过委托调用该方法。

var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");

4. 输出:这两个输出结果都是通过代理打印的

$ dotnet run
Call 1: Fabius
Call 2: Maximus

5. 多播委托

     1. 多播委托定义:多播委托是一个拥有对多个方法的引用的委托。 多播委托必须仅包含返回 void 的方法,否则将存在运行时异常。

      2. 多播委托示例

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);
        }
    }
}

3. 声明一个委托,无返回值和接收两个参数的委托, 这里有一个Oper类,它具有两个静态方法。

delegate void MyDelegate(int x, int y);

 4. 创建委托的实例,委托只想Oper类的静态Add()方法。

var del = new MyDelegate(Oper.Add);

5.将另一个方法插入到现有的委托实例中。 委托的第一次调用将调用两个方法

del += new MyDelegate(Oper.Sub);
del(6, 4);

6.从委托中删除一种方法。 委托的第二次调用仅调用一种方法。

del -= new MyDelegate(Oper.Sub);
del(2, 8);

7.输出

$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10
6.匿名方法

 1. 示例:当将匿名方法与委托一起使用时,我们可以省略方法声明。 该方法没有名称,只能通过委托来调用。

Program.
using System;

namespace Anonymous
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = delegate
            {
                Console.WriteLine("Anonymous method");
            };

            del();
        }
    }
}

2. 在这里,创建一个指向匿名方法的委托。 匿名方法的主体用{}字符括起来,但是没有名称。

MyDelegate del = delegate
{
    Console.WriteLine("Anonymous method");
};

7.C# 委托作为方法参数

委托可以用作方法参数。

示例:有一个DoOperation()方法,该方法将一个委托作为参数,把需要代理的方法传到DoOperation()方法进行调用

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;
        }
    }
}

这是一个委托声明。

delegate int Arithm(int x, int y);

这是DoOperation()方法的实现。 第三个参数是委托。 DoOperation()方法调用一个方法,该方法作为第三个参数传递给它。

static void DoOperation(int x, int y, Arithm del)
{
    int z = del(x, y);
    Console.WriteLine(z);
}

我们称为DoOperation()方法。 我们传递两个值和一个方法给它。 我们对这两个值的处理方式取决于我们通过的方法。 这就是使用委托所带来的灵活性。

DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);

输出

$ dotnet run
20
5

二、预定义的委托

        .NET 框架内置的委托

1. Action委托

Action 是.NET Framework内置的泛型委托,可以使用Action 委托以参数形式传递方法,而不用显示声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能有返回值

Action定义:Action其实就是没有返回值的delegate。

using System.Runtime.CompilerServices;

namespace System
{
    // 摘要:
    //     封装一个方法,该方法不具有参数且不返回值。
    [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
    public delegate void Action();
}

示例

Action委托至少0个参数,至多16个参数,无返回值。

Action 表示无参,无返回值的委托。

Action<int,string> 表示有传入参数int,string无返回值的委托。

Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托。

Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托。

 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}");
        }
    }

2. Func委托

        Func委托代表有返回类型的委托,Func其实就是有多个输出参数并且有返回值的delegate

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至少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类型的委托。

实例

 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;
        }
    }

三、事件 Event

1.定义

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件

 2.作用

C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。

希望一个类的某些成员在发生变化时能够被外界观测到

关键点:

  • 声明委托:定义事件将使用的委托类型。委托是一个函数签名。
  • 声明事件:使用 event 关键字声明一个事件。
  • 触发事件:在适当的时候调用事件,通知所有订阅者。
  • 订阅和取消订阅事件:其他类可以通过 += 和 -= 运算符订阅和取消订阅事件。
3. 通过事件使用委托

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

4.声明事件(Event)

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字:

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

上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。

5.示例

以下示例展示了如何在 C# 中使用事件:

using System;
using System.Diagnostics;

namespace DifferentMethods
{
    // 定义一个委托类型,用于事件处理程序
    public delegate void NotifyEventHandler(object sender, EventArgs e);

    ///  发布者类
    public class ProcessBusinessLogic
    {
        // 声明事件 
        public event NotifyEventHandler ProcessCompleted;

        // 触发事件的方法
        public virtual void OnProcessCompleted(EventArgs e)
        {
            // 执行这个事件
            if (ProcessCompleted != null)   // 如果事件已经绑定了需要执行的方法
            {
                ProcessCompleted?.Invoke(this, e);
            }
        }

        // 模拟业务逻辑过程并触发事件
        public void StartProcess()
        {
            Console.WriteLine("Process Started!");

            // 这里可以加入实际的业务逻辑

            // 业务逻辑完成,在这里触发事件
            OnProcessCompleted(EventArgs.Empty);
        }
    }

    // 订阅者类
    public class EventSubscriber
    {
        // 在这里绑定事件
        public void Subscriber(ProcessBusinessLogic process)
        {
            // 方式一
            // 先创建委托示例,在这里,将名为ProcessCompleted的事件插入到Process_ProcessCompleted()方法中。 换句话说如果触发了NotifyEventHandler事件,则将执行Process_ProcessCompleted()方法。
            process.ProcessCompleted += new NotifyEventHandler(Process_ProcessCompleted);

            // 方式二 已经通过传参拿到ProcessBusinessLogic,所以不用new
            //process.ProcessCompleted += Process_ProcessCompleted;
        }

        private void Process_ProcessCompleted(object sender, EventArgs e)
        {
            Console.WriteLine("Process Completed!");
        }
    }

    class Program
    {
        public static void Main()
        {
            ProcessBusinessLogic process = new ProcessBusinessLogic();
            EventSubscriber subscriber = new EventSubscriber();

            // 订阅事件,
            subscriber.Subscriber(process);

            // 启动过程
            process.StartProcess();
        }
    }
}

说明:

1、 定义委托

public delegate void NotifyEventHandler(object sender, EventArgs e);

这是一个委托类型,它定义了事件处理程序的签名。通常使用 EventHandler 或 EventHandler<TEventArgs> 来替代自定义的委托。

2、声明事件:这是一个使用 NotifyEventHandler 委托类型的事件。

public event NotifyEventHandler ProcessCompleted;

3、触发事件:这是一个受保护的方法,用于触发事件。使用 ?.Invoke 语法来确保只有在有订阅者时才调用事件。

protected virtual void OnProcessCompleted(EventArgs e)
{
    ProcessCompleted?.Invoke(this, e);
}

4、订阅和取消订阅事件:

// 方式一
process.ProcessCompleted += Process_ProcessCompleted;

// 方式二
process.ProcessCompleted += new NotifyEventHandler(Process_ProcessCompleted);

订阅者使用 += 运算符订阅事件,并定义事件处理程序 Process_ProcessCompleted

6.事件最常用的方式
// 事件最常用的方式:就是某个类中某些成员发生了变化,然后类的外面希望对这个类型中的成员进行监测;
var ed = new EventDemo();
// 注册事件,匿名方法
ed.MyValueChanged += () => { Console.WriteLine("value change observed"); };
ed.MyProperty = 10; // 对类型的属性进行set的时候就会触发绑定的属性; 
ed.MyProperty = 10; // 对类型的属性进行set的时候就会触发绑定的属性; 

class EventDemo
{
    // 定义一个事件
    public event Action MyValueChanged;

    private int myValue;

    public int MyProperty
    {
        get { return myValue; }
        set
        {
            myValue = value;
            // 这个属性进行set的时候就会触发这个事件
            MyValueChanged?.Invoke();
        }
    }
}

事件作为类的成员,它的特殊性在于,它只能在类的内部使用

7.事件中的参数

在C#中,.Invoke() 方法是用来调用委托(Delegate)或事件(Event)的。在事件触发时,通常会使用 .Invoke() 来调用注册的事件处理器(Event Handler)。
事件的invoke方法是不公开的,我们通常通过+=或-=操作符来添加或移除事件处理程序。然而,在某些情况下,你可能需要直接调用事件的委托。在这种情况下,如果你正在使用事件的委托,并且委托的签名包含一个额外的this参数,你需要明确提供这个参数。

this 关键字在这里表示当前对象的实例。在事件处理器中使用 this 通常是合适的,因为事件处理器是当前对象的一个方法。

EventArgs是一个特殊的 EventArgs 实例,用于指示事件不包含任何数据。当事件处理器不需要任何额外的信息时,可以使用 EventArgs.Empty

例如,考虑以下事件委托:

public delegate void MyEventHandler(object sender, EventArgs e);

在这种情况下,sender参数表示触发事件的对象实例。当你直接调用事件的委托而不是使用+=-=操作符时,你需要手动传递这个参数。

例如,如果你有一个事件MyEvent,并且你想要直接调用它的委托,你可以这样做:

public event MyEventHandler MyEvent;
 
// 调用事件的委托
MyEvent?.Invoke(this, EventArgs.Empty);

在这个例子中,this关键字表示当前对象实例,它作为sender参数传递给事件处理程序。

EventArgs是当前事件的处理参数

请注意,直接调用事件的委托通常不是一个好的实践,因为它破坏了事件的封装性,使得事件的添加和移除可以在不经过正常的+=-=操作的情况下进行。在大多数情况下,你应该仅通过操作符来管理事件。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐