通过委托的实际应用理解委托

本文详细阐述了C#中委托和事件的妙用,并通过实例展示了如何利用委托解决窗体间传值问题。同时,介绍了如何使用委托统一异常处理流程,进一步探讨了使用泛型委托来简化代码重构过程。最后,文章总结了异常处理的最佳实践,包括自定义异常类的创建和统一异常管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

        一晃已经工作好几个月了,一直想没搞明白委托和事件到底有什么妙用。记得刚开始学习C#时,入门也就一个星期的时间,只是匆匆过了一遍语法。学到委托和事件时,就看了博客园张子阳的委托和事件,认识就停留在网上猫大叫的题目上,一直没用过也就没有体会到什么妙处。后来开始找工作了,再看那篇文章还是似懂非懂,不用又忘了。也许领悟力欠缺了点,直到最近遇到实际应用才稍微有些明朗。

       当时对于委托,我不明白的地方主要有:1委托是一种类型(现在想想,这就是规定);2为什么要用委托(因为当时看到的那些例子明明就可以不用委托,一样简洁);3委托的优势(或者说妙用);4还不明白谁来调用委托,委托到底定义在哪,只知道按着张子阳那个例子把猫大叫的例子写好,两个都是控制台程序,都是在Main函数中绑定操作的。

        第一次遇到的问题是Form窗体间传值问题:一个主窗体FormMain,显示信息,然后我想弹出信息设置窗体,在弹出的FormNew窗体中修改信息值保存到FormMain中。这个例子在WinForm中这个应用还是很常见的。

        因为第一次遇到,想当然的就new一个FormMain,然后将值改变,一运行发现重新弹出一个FormMain窗体,再看程序原来是没有获得原来那个FormMain对象,于是乎就传了个This给新窗体。由于是第一次遇到还有些沾沾自喜,发现自己能够在两个页面之间任意传值了。后来晚上回去随意查了查页面间传值,一查发现我有收获了,原来别人早就遇到这种情况了,而且还是个问题,我的方法太浪费了,不值得,各种留言中总共有三个版本,一个是我这种方法,一个是主窗体静态控件,还有一个是委托和事件。静态控件很容易解决,当时就不明白怎么用委托实现,于是又看张子阳的事件与委托,一个晚上才弄明白,会了也就感觉简单了。

       先上代码:

      FormMain窗体:一个Lable,一个TextBox,一个Button,点击Button弹出FormNew窗体

using System;

using System.Windows.Forms;

namespace DelegateDemo20111119
{
    public delegate void ChangeDelegate(string stringValue);

    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
        }

         private void btn_NewForm_Click(object sender, EventArgs e)
        {
            FormNew frmNew = new FormNew();
            frmNew.changeValue = ChangeValue;
            frmNew.ShowDialog();
        }
        private void ChangeValue(string textValue)
        {
            this.tb_Value.Text = textValue;
        }
    }
}

      FormNew窗体:一个Lable,一个TextBox,一个Button,点击Button将TextBox中的内容传到FormMain窗体中。

using System;
using System.Windows.Forms;

namespace DelegateDemo20111119
{
    public partial class FormNew : Form
    {
        public ChangeDelegate changeValue;

        public FormNew()
        {
            InitializeComponent();
        }

        private void btn_OK_Click(object sender, EventArgs e)
        {
            if (changeValue != null)
                changeValue(tb_NewValue.Text);
        }
    }
}

         这里要注意的:委托定义的位置是在命名空间内,在同一个命名空间内都可以用这个委托类型,不理解的可以想象在命名空间内定义结构或者枚举类型,地位是等价的。

         其实上面的例子还是不能够明确表明委托是什么,在网上有些人说可以用指针来理解委托,委托变量绑定方法时直接获得了方法在内存中的地址,因此委托无需通过类来查找方法的地址。而其他类,如果没有操作该类的权限(即无法获得该类的内存地址,也就无法通过类来查找方法的内存地址),也就无法操作该类的方法。有了委托,我可以只关心需要操作的那个方法,将他绑定给委托变量即可。上面例子FormNew类其实并不关心FormMain,只关心的是将FormMain上的TextBox文本框的值更新一下,也就是只关心ChangeValue这个函数。frmNew.changeValue = ChangeValue将ChangValue方法绑定到委托,要更新时就直接调用委托,该委托保存的是ChangeValue方法的内存地址,也就直接操作的ChangeValue方法。

       后来查了委托的妙用,找了好久好久,才在优快云论坛一个留言中看到有人说底层API应用中常常用到,比如网卡、声卡等,我们不能操作网卡声卡,但对外有操作的方法,只要传入参数即可。我想到了以前学的一钩子程序用到了API,就跟这个类似,当时只知道依样画葫芦传入一方法名,现在串起来理解,容易多了。于是模仿着把这个例子改了改,在命名空间下新建了一个DelegateHelper类,有一静态委托变量changeDelegate。在FormMain实例化时就将ChangeValue方法绑定到changeDelegate委托上,以后别的窗体要改变FormMain的状态,只需调用changeDelegate即可,而不需管能不能直接操作FormMain,只要委托不为空就说明已经绑定了,就可直接改变FormMain的相关信息。

       DelegateHelper类:

using System;

namespace DelegateDemo20111119
{
    public class DelegateHelper
    {
        public static ChangeDelegate changeDelegate;
    }
}

        FormMain类:

using System;
using System.Windows.Forms;

 namespace DelegateDemo20111119
{
    public delegate void ChangeDelegate(string stringValue);

    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
            DelegateHelper.changeDelegate = ChangeValue;
        }

        private void btn_NewForm_Click(object sender, EventArgs e)
        {
            FormNew frmNew = new FormNew();
            frmNew.ShowDialog();
        }
        private void ChangeValue(string textValue)
        {
            this.tb_Value.Text = textValue;
        }
    }
}


        FormNew类:

using System;
using System.Windows.Forms;

 namespace DelegateDemo20111119
{
    public partial class FormNew : Form
    {
        public FormNew()
        {
            InitializeComponent();
        }

        private void btn_OK_Click(object sender, EventArgs e)
        {
            if (DelegateHelper.changeDelegate != null)
                DelegateHelper.changeDelegate(tb_NewValue.Text);
        }
    }
}


       第二次遇到纯粹是巧合,我那天重构代码时,想解决消除许多if else的问题,搜到博客园谢平的这篇文章,看到下边讲了个表驱动处理异常,而我一直也不会全局处理异常,只会局部的try catch finally,顿时大感兴趣。于是我就先原封不动的照搬了,类的结构参照的他的(还是说抄吧),参考的思想我就不重复讲思想了。一般自定义的异常继承自ApplicationException,且在低层手动抛出,在高层捕获。

      先上异常结构的代码:

     自定义异常基类: MyException.cs

using System;

 namespace ExceptionHelper
{
    public class MyException:ApplicationException
    {
        private string className = "MyException";
        private string message;
        public virtual   short Index
        {
            get { return 0; }
        }

        public virtual void TreateException(Exception ex)
        {
        }
        public MyException()
            : base()
        {
        }

        public MyException(string message):base(message)
        {
            this.message = className+ message;
        }
        public MyException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }
}


     自定义异常类一: MyFirstException.cs

using System;

 namespace ExceptionHelper
{
    public class MyFirstException:MyException
    {
        private string className = "MyFirstException";
        private string message;
        public override short Index
        {
            get { return 1; }
        }

        public override void TreateException(Exception ex)
        {
        }

        public MyFirstException()
            : base()
        {
        }

        public MyFirstException(string message):base(message)
        {
            this.message = className+message;
            Console.WriteLine(this.message);
        }

        public MyFirstException(string message, Exception innerException)
            : base(message, innerException)
        {
         }
    }
}


        我只讲讲我添加的部分,类的结构是有了,我就想如何对这些进行统一管理,让代码改变时无需牵一发而动全身。

根据他的思想先添加一个管理类,为了控制只有一个异常管理字典,我考虑用静态构造函数实现,异常管理字典的值是委托类型的,如下所示:

using System;
using System.Collections.Generic;

namespace ExceptionHelper
{
    public delegate void ForException(Exception ex);
    public  class ExceptionManage
    {
        public static Dictionary<short, ForException> dictException;
        static ExceptionManage()
        {
            dictException = new Dictionary<short, ForException>();
            dictException.Add(0, new MyException().TreateException);
            dictException.Add(1, new MyFirstException().TreateException);
        }
    }
}

        在业务高层时捕获异常

try
{
        //这里写需要执行的语句
}
catch (MyException ex)
{
        MyException myException = ex as MyException;
        short index = myException.Index;
        ExceptionManage.dictException[index].Invoke(ex);
}
catch (Exception ex)
{
        Console.WriteLine(ex.StackTrace);
}

        至此,他的思想全部完成。问题在于现在我的每个高层业务中都有这样一个程序段,虽统一,但还是存在重复,代码还需重构。于是我想起了委托,将函数作为参数,那样只需调用一个函数,参数就是原来的函数名。把高层函数中的语句都提取出成函数,不现实,那样太假了(自我感觉,感觉那样有点重复),相当于把所有的高层全部重新写了一遍。其实会出异常的也就那么几句(甚至只有一句),因为好多都只是一串语句中的一句,可以把那几句抽取出来。然而这些从中间抽取语句可不容易,有的功能是改变状态,有的只是执行,还有的需要返回值。于是我根据业务写了若干委托(因为绑定委托时参数都要相对应),为了少写几个函数,我还稍微调整了几个函数参数的顺序,最可恨的就是有返回值的,还要传引用。

    public static void TryExcute(DelegateOne TryMethod)
    {
        try
        {
                TryMethod();
        }
        catch (MyException ex)
        {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
        }
        catch (Exception ex)
        {
                Console.WriteLine(ex.StackTrace);
        }
    }

    //无参,有一个string类型的返回值
     public static void TryExcute(DelegateRefOne TryMethod,ref string arg)
    {
        try
        {
                arg =TryMethod();
        }
        catch (MyException ex)
        {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
        }
        catch (Exception ex)
        {
                Console.WriteLine(ex.StackTrace);
        }
    }

        再次重构时,我就在想,如果高层添加了业务出现了新种类的函数,还得在这边添加,能不能用泛型呢,想起不久前看到的VS自带有Action,MSDN上一查,把所有的不带参数的都统一了,当时也就没有再考虑有参数的了,晚上回去之后我就想,为什么微软不提供一个有返回值的泛型委托呢,其实是我没搜,我一搜果然有,Func可以解决。于是管理类改进为如下。

using System;
using System.Collections.Generic;

namespace ExceptionHelper
{
    public delegate void ForException(Exception ex);
    public  class ExceptionManage
    {
        public static Dictionary<short, ForException> dictException;
        static ExceptionManage()
        {
            dictException = new Dictionary<short, ForException>();
            dictException.Add(0, new MyException().TreateException);
            dictException.Add(1, new MyFirstException().TreateException);
        }

        public static void TryExcute(Action TryMethod)
        {
            try
            {
                TryMethod();
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }

        public static void TryExcute<T>(Action<T> TryMethod,T arg)
        {
            try
            {
                TryMethod(arg);
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }

        public static void TryExcute<S,V>(Action<S,V> TryMethod, S arg1,V arg2)
        {
            try
            {
                TryMethod(arg1, arg2);
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }

        public static void TryExcute<R>(Func<R>TryMethod, R result)
        {
            try
            {
                result=TryMethod();
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }

        public static void TryExcute<T, R>(Func<T, R> TryMethod, T arg, R result)
        {
            try
            {
                result = TryMethod(arg);
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }

        public static void TryExcute<S, T, R>(Func<S, T, R> TryMethod, S arg1, T arg2, R result)
        {
            try
            {
                result = TryMethod(arg1, arg2);
            }
            catch (MyException ex)
            {
                MyException myException = ex as MyException;
                short index = myException.Index;
                ExceptionManage.dictException[index].Invoke(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.StackTrace);
            }
        }
    }
}

        注:1Action和Func都在System命名空间下。2泛型当有几个参数时要用不同的字母区分。3委托的 Invoke方法表示直接执行委托。4 异常的StackTrace表示详细的异常信息,比较有价值。5Func中R类型的参数表示返回值,用out实现的。

       至此全部完成,只是个人的一些理解,希望能对初学者有些帮助,如果有什么错误,还望路过的大牛们不吝指教。转载请保留原文链接,点此源代码下载

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值