C#中的委托自我总结

委托这个东西不是很好理解,可是工作中又经常用到,你随处可以看到它的身影,真让人有一种又爱又恨的感觉,我相信许多人被它所困扰过。

什么是委托?委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针。委托可以把函数做为参数传递,其实际意义便是让别人代理你的事情。委托可以看做是函数的指针,整数可以用整数变量指向它,对象可以用对象变量指向它,

函数也可以用委托变量指向它。我们可以选择将委托类型看做只定义了一个方法的接口,而委托的实例可以看做是实现了那个接口的一个对象。

使用委托,必须满足4个条件:

  • 声明委托类型;
  • 必须有一个方法包含了要执行的代码;
  • 必须创建一个委托实例;
  • 必须调用(invoke)委托实例。

委托的申明

声明委托的方式:delegate 返回值类型 委托类型名(参数)

委托的申明和接口方法的申明基本上一致,只是在返回类型关键字的前面多了一个delegate关键字。还有就是委托一般声明为public类型,因为它随时要供别人调用的。

委托的本质也是一个类型。我们声明一个类可以进行实例化,同样委托也可以进行实例化。

有如下四种委托:

1

2

3

4

5

6

7

8

//1.无参数无返回值

    public delegate void NoParaNoReturnEventHandler();

    //2.有参数无返回值

    public delegate void WithParaNoReturnEventHandler(string name);

    //3.无参数有返回值

    public delegate string NoParaWithReturnEventHandler();

    //4.有参数有返回值

    public delegate string WithParaWithReturnEventHandler(string name);

如果代码想要执行操作,但不知道操作细节,一般可以使用委托。例如, Thread类之所以知道要在一个新线程里运行什么,唯一的原因就是在启动新线程时,向它提供了一个ThreadStart或ParameterizedThreadStart委托实例。

1

2

3

4

Thread th = new Thread(Test);

th.Start();

public Thread(ThreadStart start);

public delegate void ThreadStart();

ThreadStart是一个无参无返回值的委托。

1

2

3

4

static void Test()

{

  Console.WriteLine("线程方法");

}

这个Test方法的函数签名必须和委托ThreadStart的函数签名一致。

委托的调用

必须先实例化委托,然后再调用。

函数的签名和委托的签名必须一致。NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo;,编译器帮我们进行了new,但是不能写成NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo();

因为这样就成为了函数调用。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

#region 无返回值委托调用

    public static void Show()

    {

      //实例化委托

      NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = new NoParaNoReturnEventHandler(ConsoleInfo);

      //NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo; //简写

      //委托调用 通过Invoke()调用,或者可以直接省略

      _NoParaNoReturnEventHandler.Invoke();

      //_NoParaNoReturnEventHandler();

    }

    private static void ConsoleInfo()

    {

      Console.WriteLine("无参数无返回值的函数调用");

    }

    #endregion

没有委托就没有异步,异步正是因为委托的存在。

_NoParaNoReturnEventHandler.BeginInvoke(null,null); //异步调用

为什么要使用委托

我们完全可以直接调用方法,为什么还需要通过一个委托来调用呢?委托有什么意义?

解耦,对修改关闭,对扩展开放。逻辑分离。

你可以把委托理解为函数的父类,或者是一个方法的占位符。

我们来看下代码,假设有2个方法,一个说英语,一个说汉语,而这2个方法的函数签名是一样的。

1

2

3

4

5

6

7

8

public static void SayChinese(string name)

    {

      Console.WriteLine("你好," + name);

    }

    public static void SayEnglish(string name)

    {

      Console.WriteLine("hello," + name);

    }

那么我们在外部调用的时候,

1

2

MyDelegate.SayChinese("张三");

MyDelegate.SayEnglish("zhangsan");

如果要调用这两个不同的方法,是不是要写不同的调用代码

我们能不能只一个方法调用呢?修改代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

public static void Say(string name,WithParaNoReturnEventHandler handler)

    {

      handler(name);

    }

   public static void SayChinese(string name)

    {

      Console.WriteLine("你好," + name);

    }

    public static void SayEnglish(string name)

    {

      Console.WriteLine("hello," + name);

    }

这样,只通过一个方法Say来进行调用。

如何调用呢?如下三种调用方式: 

1

2

3

4

WithParaNoReturnEventHandler _WithParaNoReturnEventHandler = new WithParaNoReturnEventHandler(MyDelegate.SayChinese);

MyDelegate.Say("张三",_WithParaNoReturnEventHandler);

MyDelegate.Say("张三", delegate(string name) { Console.WriteLine("你好," + name); }); //匿名方法

MyDelegate.Say("张三", (name) => { Console.WriteLine("你好," + name); }); //lambda表达式

以上代码使用了几种调用方式,这些调用方式都是随着C#的升级而不断优化的。第一种是C#1.0中就存在的传统调用方式,第二种是C#2.0中的匿名方法调用方式,所谓匿名方法,就是没有名字的方法,当方法只调用一次时使用匿名方法最合适不过了。C#3中的lambda表达式。其实泛型委托同样是被支持的,而.NET 3.5则更进一步,引入了一组名为Func的泛型委托类型,它能获取多个指定类型的参数,并返回另一个指定类型的值。

lambda表达式

lambda表达式的本质就是一个方法,一个匿名方法。

如果方法体只有一行,无返回值,还可以去掉大括号和分号。

MyDelegate.Say("张三", (name) => Console.WriteLine("你好," + name));

如果方法体只有一行,有返回值,可以去掉大括号和return。

WithParaWithReturnEventHandler _WithParaWithReturnEventHandler = (name)=>name+",你好";

从.NET3.5开始,基本上不需要我们自己来申明委托了,因为系统有许多内置的委托。

Action和Func委托,分别有16个和17个重载。int表示输入参数,out代表返回值,out参数放置在最后。

Action表示无返回值的委托,Func表示有返回值的委托。因为方法从大的角度来分类,也分为有返回值的方法和无返回值的方法。

也就是说具体调用什么样的方法,完全由调用方决定了,就有了更大的灵活性和扩展性。为什么这么说,如果我有些时候要先说英语再说汉语,有些事时候要先说汉语再说英语,如果没有委托,我们会怎么样实现?请看如下代码:

1

2

3

4

5

6

7

8

9

10

public static void SayEnglishAndChinese(string name)

    {

      SayEnglish(name);

      SayChinese(name);

    }

    public static void SayChineseAndEnglish(string name)

    {

      SayChinese(name);

      SayEnglish(name);

    }

如果又突然要添加一种俄语呢?被调用方的代码又要修改,如此循环下去,是不是要抓狂了?随着不断添加新语种,代码会变得越来越复杂,越来越难以维护。这样的代码耦合性非常高,是不合理的,也就是出现了所谓的代码的坏味道,你可以通过设计模式(如观察者模式等),在不使用委托的情况下来重构代码,但是实现起来是非常麻烦的,要写很多更多的代码...

委托可以传递方法,而这些方法可以代表一系列的操作,这些操作都由调用方来决定,就很好扩展了,而且十分灵活。我们不会对已有的方法进行修改,而是只以添加方法的形式去进行扩展。

可能有人又会说,我直接在调用方那里来一个一个调用我要执行哪些方法一样可以实现这样的效果啊?

可你有没有想过,你要调用的是一系列方法,你根本无法复用这一系列的方法。使用委托就不一样了,它好比一个方法集合的容器,你可以往里面增减方法,可以复用的。而且使用委托,你可以延时方法列表的调用,还可以随时对方法列表进行增减。委托对方法进行了再一次的封装。

总结:也就是当你只能确定方法的函数签名,无法确定方法的具体执行时,为了能够更好的扩展,以类似于注入方法的形式来实现新增的功能,就能体现出委托的价值。

委托和直接调用函数的区别:用委托就可以指向任意的函数,哪怕是之前没定义的都可以,而不用受限于哪几种。

多播委托

组合的委托必须是同一个类型,其相当于创建了一个按照组合的顺序依次调用的新委托对象。委托的组合一般是给事件用的,用普通委托的时候很少用。

通过+来实现将方法添加到委托实例中,-来从委托实例中进行方法的移除。

+和-纯粹是为了简化代码而生的,实际上其调用的分别是Delegate.Combine方法和Delegate.Remove。

如果委托中存在多个带返回值的方法,那么调用委托的返回值是最后一个方法的返回值。

1

2

3

4

5

6

7

8

9

10

11

public static void MultipleShow()

    {

      //多播委托

      NoParaWithReturnEventHandler _NoParaWithReturnEventHandler = new NoParaWithReturnEventHandler(GetDateTime);

      _NoParaWithReturnEventHandler += GetDateTime;

      Console.WriteLine(_NoParaWithReturnEventHandler());

    }

    public static string GetDateTime()

    {

      return string.Format("今天是{0}号。", DateTime.Now.Day.ToString());

    }

委托总结:

  • 委托封装了包含特殊返回类型和一组参数的行为,类似包含单一方法的接口;
  • 委托类型声明中所描述的类型签名决定了哪个方法可用于创建委托实例,同时决定了调用的签名;
  • 为了创建委托实例,需要一个方法以及(对于实例方法来说)调用方法的目标;
  • 委托实例是不易变的,就像String一样;
  • 每个委托实例都包含一个调用列表——一个操作列表;
  • 事件不是委托实例——只是成对的add/remove方法(类似于属性的取值方法/赋值方法)。

常见使用场景:窗体传值、线程启动时绑定方法、lambda表达式、异步等等。

C#跨窗体传递数据的几种方法分析

创建一个Winform窗体应用程序项目,然后添加一个Form2窗体。

在Form1和Form2中各添加一个textBox和button:

单击Form1中的button1,弹出Form2,然后要做的就是在Form1中的textBox1和Form2中的textBox2中传值。

为了方便起见,将Form1称作父窗体,将Form2称作子窗体。

相对来说,将父窗体的值传到子窗体较为容易实现。下面分别进行说明。

一、父窗体传值给子窗体。

方法1:通过Form类构造方法的重载传参。

Form1类中代码:

 using System;
 using System.Windows.Forms;
 
 namespace WindowsForms
 {
      public partial class Form1 : Form
      {
          public Form1()
         {
            InitializeComponent();
         }

         private void button1_Click(object sender, EventArgs e)
         {
             Form2 f2 = new Form2(textBox1.Text);
             f2.ShowDialog();
         }  
      }
 }


Form2类中代码:

  using System;
  using System.Windows.Forms;
  
  namespace WindowsForms
  {
      public partial class Form2 : Form
      {
          public Form2()
          {
             InitializeComponent();
          }
 
          public Form2(string str)
          {        
             InitializeComponent();
             textBox2.Text = str;//这句必须放在InitializeComponent();的后面,否则会引起“空引用异常”
          }
       }
  }


方法2:通过外部可访问的中间变量传参。

先看一串无法实现目的的代码。

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public  string str;//public类型的实例字段

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            str = textBox1.Text;//注意,这句不能放在f2.ShowDialog();的后面,否则会先执行textBox2.Text = f1.str;再执行str = textBox1.Text;
            f2.ShowDialog();
        }  
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void Form2_Load(object sender, EventArgs e)
        {
            Form1 f1 = new Form1();
            textBox2.Text = f1.str;
        }

    }
}



结果值没有传过来:

这是因为:Form1 f1 = new Form1();相当于新建了一个对象,这个新对象的str初始值是null(只有该对象的button1被click的时候才会改变str的值)。textBox2.Text = f1.str;其实就是将null赋给了textBox2.Text。

static变量可以认为是全局变量,static变量被所有对象所共有,也可以被所有对象所改变,将相关代码修改如下即可:

public static string str;//public类型的实例字段

private void Form2_Load(object sender, EventArgs e)
{ 
   textBox2.Text = Form1.str;
}


它有一种变式:
Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.str = textBox1.Text;
            f2.ShowDialog();

        }
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        public string str;//这里str是在Form2类中定义的,并且不需要static关键字

        private void Form2_Load(object sender, EventArgs e)
        {
            textBox2.Text = str;
        }

    }
}


分析: f2.str = textBox1.Text;将textBox1的值赋给f2.str,由于f2.ShowDialog();跳出来的刚好是这个对象,因此这个对象的str值不为空。最终通过textBox2.Text = str;将值传过去了。跟上一个例子的不同之处是:str声明的位置不一样,一个是在Form1类中,一个是在Form2类中。

二、子窗体传值给父窗体。

方法1:通过Owner设置窗口归属来传值。

① 通过Controls

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ShowDialog(this);//这个this必不可少(将窗体显示为具有指定所有者:窗体f2的所有者是Form1类当前的对象)
        }
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form1 f1 = (Form1) this.Owner;//将本窗体的拥有者强制设为Form1类的实例f1
            f1.Controls["textBox1"].Text = textBox2.Text;
        }

    }
}


② 通过在控件所在类中增加改变控件属性的方法

或者通过在Form1中添加一个ChangeText方法也行:

public void ChangeText(string str)
{
   textBox1.Text = str;
}


Form2中代码相应改变为:

private void button2_Click(object sender, EventArgs e)
{
    Form1 f1 = (Form1) this.Owner;//将本窗体的拥有者强制设为Form1类的实例f1
    f1.ChangeText(textBox2.Text);
}


注意:如果不用Owner,仅仅想用下面的代码实现,是没可能的。

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ShowDialog();
        }

        public void ChangeText(string str)
        {
            textBox1.Text = str;
        }
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form1 f1 =new Form1();
            f1.ChangeText(textBox2.Text);
        }
    }
}


这种代码无法实现目的

③ 通过将外界不可访问的控件“属性”封装成可以访问的属性。

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ShowDialog(this);
        }

        public string TextBox1Value
        {
            set { textBox1.Text = value; }
            get { return textBox1.Text; }
        }

    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Form1 f1 =(Form1)this.Owner;
            f1.TextBox1Value = textBox2.Text;
        }

    }
}


方法2:通过提供外部可访问的属性和DialogResult的状态来传值(这个例子同时实现了父传子、子传父)。

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {          
            Form2 f2 = new Form2();
            f2.TextBox2Value = textBox1.Text;//顺便把父窗体的值先传给子窗体

            //这一行不需要再写f2.ShowDialog();或者f2.Show();,否则f2会弹出来两次

            if (f2.ShowDialog() == DialogResult.OK)//这样的语句是合法的:DialogResult f = f2.ShowDialog();
            {
                textBox1.Text = f2.TextBox2Value;
                //f2.Close();//这一句写或者不写,f2都会关闭
            }  
        }    
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponen
        }

        public string TextBox2Value //将外部不可访问的textBox2.Text封装成属性TextBox1Value
        {
            set { textBox2.Text = value; }
            get { return textBox2.Text; }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;//这里的DialogResult是Form2类对象的属性
        }

    }
}


方法3:通过委托事件来传值。(这种方式可以实现不同控件数据实时联动的复杂功能)

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ChangeText += new ChangeTextHandler(Change_Text);//将事件和处理方法绑在一起,这句话必须放在f2.ShowDialog();前面
            f2.ShowDialog();            
        }

       public void Change_Text(string str)
        {
            textBox1.Text = str;
        }  
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public delegate void ChangeTextHandler(string str);  //定义委托

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

        public event ChangeTextHandler ChangeText;  //定义事件

        private void button2_Click(object sender, EventArgs e)
        {
            if (ChangeText != null)//判断事件是否为空
            {
                ChangeText(textBox2.Text);//执行委托实例  
                this.Close();
            }           
        }    
    }
}


可以用.NET提供的Action<>泛型委托和lambda表达式简化上面这个例子:

Form1中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 f2 = new Form2();
            f2.ChangeText = (str) => textBox1.Text = str;//用lambda表达式实现,这句话必须放在f2.ShowDialog();前面
            f2.ShowDialog();            
        }
    }
}


Form2中:

using System;
using System.Windows.Forms;

namespace WindowsForms
{  
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }

        public Action<string> ChangeText;//之前的定义委托和定义事件由这一句话代替

        private void button2_Click(object sender, EventArgs e)
        {
            if (ChangeText != null)//判断事件是否为空
            {
                ChangeText(textBox2.Text);//执行委托实例  
                this.Close();
            }           
        }    
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值