CSharp中委托(一)委托、匿名函数、lambda表达式、多播委托、窗体传值、泛型委托

委托就是一种类型:一种表示标明函数de类型的类。

因此,可将函数作为参数变量传来传去。

将函数作为参数传递给委托类型的变量,相当于将函数看做参数传入函数中。

符号:=>:就是将参数传递给函数(匿名的话就是:函数体)

定义一个委托,相当于定义了一个类。因此写在类外面。

声明委托类型的类时,这个类,要跟被委托的函数有相同的签名(参数和返回值)。

要用这个委托类型的类去声明变量,以此变量用来传递函数。

被委托的函数:将要作为参数传递的函数。

委托

先来个例子:

三个需求:
1、将一个字符串数组中每个元素都转换成大写
2、将一个字符串数组中每个元素都转换成小写
3、将一个字符串数组中每个元素两边都加上 双引号

string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
StrToUpper(names);
//StrSYH(names);

for (int i = 0; i < names.Length; i++)
{
    Console.WriteLine(names[i]);
}

用三个方法分别去实现。

public static void StrToUpper(string[] name)
{
    for (int i = 0; i < name.Length; i++)
    {
        name[i] = name[i].ToUpper();
    }       
}

public static void StrToLower(string[] name)
{
    for (int i = 0; i < name.Length; i++)
    {
        name[i] = name[i].ToLower();
    }
}

public static void StrSYH(string[] name)
{
    for (int i = 0; i < name.Length; i++)
    {
        name[i] = "\"" + name[i] + "\"";
    }
}

三个方法有大量相同重复代码,只有少部分是不同的。而且写完方法,三个功能要用三个函数,我们希望一个函数搞定。

首先解决用一个函数来实现这三个功能,将下面的三个函数名作为参数,来输入到这个函数中。用哪个函数名,就实现哪个函数的功能。

那么将函数名作为实参传入函数的这种操作,我们称为委托

实质:将函数名作为参数传入函数中。将原函数要实现的功能,委托给一个新函数来实现。函数指针。指向函数。

函数名作为一个传递给一个变量,这个变量必定有一个类型,这个类型就是我们提到的委托类型:新的类型关键字:delegate

声明一个委托类型,委托所指向(非常类似指针)的函数,必须跟委托具有相同的签名(参数和返回值)。

(C/C++中,指针指向的变量类型相同,int*类型的指针指向int类型变量,char*类型的指针指向char类型的变量)

C语言中:函数指针就是指向函数指针

这个委托可以看做是一个指向函数的指针类型

回到我们的需求案例中,如上,函数没有返回值,但是有一个string[]类型的输入。委托写的位置是:类外,命名空间里面。因此声明委托如下:

public delegate void DelStrTo(string[] name);

写一个方法,将需要委托的函数作为参数传入,相当于传递一个方法过来

这个参数的类型为刚才声明的委托类型:即:DelStrTo类型

标明函数这个参数位置需要传入一个什么类型(函数签名相同)的函数方法:

public static void Test(string[] name, DelStrTo del)
{
    del(name); //委托需要一个参数,写入name。
}

本来这个方法是要调用上面那三个函数来实现对应的功能。为了传递这三个方法,需要使用委托类型的参数。委托类型的参数里面(赋值)的就是三个函数中的其中一个。 

接下来,我们调用Test方法。

委托的第一种使用方法:声明委托指向函数。

string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
//StrToUpper(names);
//StrSYH(names);

//DelStrTo del = new DelStrTo(StrToUpper);  // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
//DelStrTo del = new DelStrTo(StrToLower);
DelStrTo del = new DelStrTo(StrSYH);
del(names);

for (int i = 0; i < names.Length; i++)
{
    Console.WriteLine(names[i]);
}

委托的第二种写法:直接把方法赋值给一个委托类型。

string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
//StrToLower(names);
//StrToUpper(names);
//StrSYH(names);

//DelStrTo del = new DelStrTo(StrToUpper);  // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
//DelStrTo del = new DelStrTo(StrToLower);
//DelStrTo del = new DelStrTo(StrSYH);
DelStrTo del = StrToUpper;    // 方法直接赋值给委托类型。
DelStrTo del = StrToLower;
DelStrTo del = StrSYH;

del(names);

for (int i = 0; i < names.Length; i++)
{
    Console.WriteLine(names[i]);
}

委托的第三种写法:既然可以赋值,那么就可以作为参数传递。

把一个方法作为参数,传递给另一个方法,那么参数应该有类型。方法作为参数的类型就是委托类型。

把函数赋值给一个委托类型,前提是这个委托的签名跟函数相同。

static void Main(string[] args)
{
    //三个需求
    //1、将一个字符串数组中每个元素都转换成大写
    //2、将一个字符串数组中每个元素都转换成小写
    //3、将一个字符串数组中每个元素两边都加上 双引号

    string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
    //StrToLower(names);
    //StrToUpper(names);
    //StrSYH(names);

    //DelStrTo del = new DelStrTo(StrToUpper);  // 创建一个委托对象,它指向了一个函数。类似于Thread用法,传入一个函数。
    //DelStrTo del = new DelStrTo(StrToLower);
    //DelStrTo del = new DelStrTo(StrSYH);

    //DelStrTo del = StrToUpper;    // 直接把方法赋值给委托。
    //DelStrTo del = StrToLower;
    //DelStrTo del = StrSYH;
    //del(names);

    Test(names, StrToUpper);
    Test(names, StrToLower);
    Test(names, StrSYH);

    for (int i = 0; i < names.Length; i++)
    {
        Console.WriteLine(names[i]);
    }

}

上面说了半天不同写法。绕来绕去,还是不如最初的写法,直接调用方法多好。为此我们需要一点新的东西。


匿名函数

对于Test函数我们重新写一个:将上面三个函数共同的代码提取出来。

public static void Test(string[] names, DelStrTo del)
{
    //del(name); //委托需要一个参数,写入name。
    for (int i = 0; i < names.Length; i++)
    {
        names[i] = del(names[i]);
    }
}

我们希望用委托将三个需求功能实现代码中不同的部分,委托掉。但是编译器此时会提醒。del无法从“string”转换为“string[]”。因此考虑字符串数组中,每个元素都是字符串,我们可以把委托输入的参数写为字符串string,而不是原来的字符串数组string[]。而且我们test方法已经遍历了,原来的方法只是处理每个元素,因此委托输入的参数应该为string类型,而不是string[]类型。并且有返回值string,而不是void。

public delegate string DelStrTo(string name);

此时洋气的写法有了。我们用匿名函数,也就是delegate里面以没有命名的方式实现了一个函数的方法。既有委托,又包含不同的实现方法。代码量大大减少。公共部分包含在test中,差异部分用匿名函数来简化。

public delegate string DelStrTo(string name);

class Program
{
    static void Main(string[] args)
    {
        //三个需求
        //1、将一个字符串数组中每个元素都转换成大写
        //2、将一个字符串数组中每个元素都转换成小写
        //3、将一个字符串数组中每个元素两边都加上 双引号

        string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };

        Test(names, delegate (string name) { return name.ToUpper(); });
        Test(names, delegate (string name) { return name.ToLower(); });
        Test(names, delegate (string name) { return "\"" + name + "\""; });

        for (int i = 0; i < names.Length; i++)
        {
            Console.WriteLine(names[i]);
        }

    }

    public static void Test(string[] name, DelStrTo del)
    {
        //del(name); //委托需要一个参数,写入name。
        for (int i = 0; i < name.Length; i++)
        {
            name[i] = del(name[i]);
        }
    }

}

当方法只写一次,只用一次的时候,匿名函数是非常省力的方式。

嵌套写在里面可能有点复杂,我们看一个简单的例子。

我们考虑一个问题,Test函数还有用吗?

Test函数只是帮我们传递了一个委托,我们不用Test来传来传去了。Test的功能我们也直接用匿名函数来实现。。

DelStrTo del = delegate (string[] names)
{
    for (int i = 0; i < names.Length; i++)
    {
        names[i] = names[i].ToUpper();
    }
};
del(names);

需要函数干的事情,交给委托,然后匿名就好了。委托和匿名函数的签名也必须一致。如果委托是string类型的,那么匿名函数,就需要有return。而此处我们只转换。

lambda表达式

还有一个洋气的写法:lambda表达式:匿名函数更为简写的形式。

static void Main(string[] args)
{
    //三个需求
    //1、将一个字符串数组中每个元素都转换成大写
    //2、将一个字符串数组中每个元素都转换成小写
    //3、将一个字符串数组中每个元素两边都加上 双引号

    string[] names = { "abCDefG", "HIJKlmnOP", "QRsTuvW", "XyZ" };
    

    //DelStrTo del = delegate (string[] names)
    //{
    //    for (int i = 0; i < names.Length; i++)
    //    {
    //        names[i] = names[i].ToUpper();
    //    }
    //};
    DelStrTo del  = (string[] names) =>
    {
        for (int i = 0; i < names.Length; i++)
        {
            names[i] = names[i].ToUpper();
        }
    };
    del(names);

    for (int i = 0; i < names.Length; i++)
    {
        Console.WriteLine(names[i]);
    }

}

lambda表达式中如果没有参数,可以将参数省去,但是括号不能省略。

符号=>表示goes to:将参数传向方法体。

看几个lambda表达式的例子。 

namespace _12_lambda表达式
{

    public delegate void DelOne();
    public delegate void DelTwo(string name);
    public delegate string DelThree(string name);
    class Program
    {
        static void Main(string[] args)
        {
            DelOne del = () => { // dosomething,比如打印申明 };// delegate() { };

            DelTwo del2 = (string name) => { };//delegate(string name) { };

            DelThree del3 = (string name) => { return name; };//delegate(string name) { return name; };


            List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            list.RemoveAll(n => n > 4);    //条件筛选器。
            foreach (var item in list)
            {
                Console.WriteLine(item);
            }
            Console.ReadKey();
        }
    }
}

多播委托

委托不仅可以指向一个函数,还可以指向多个函数。

public delegate void DelTest();
class Program
{
    static void Main(string[] args)
    {
        DelTest del = T1;
        del += T2;
        del += T3;
        del += T4;
        del -= T3;
        del -= T1;
        del();
        Console.ReadKey();
    }

    public static void T1()
    {
        Console.WriteLine("我是T1");
    }
    public static void T2()
    {
        Console.WriteLine("我是T2");
    }

    public static void T3()
    {
        Console.WriteLine("我是T3");
    }
    public static void T4()
    {
        Console.WriteLine("我是T4");
    }
}

窗体传值

场景表述:

窗体1有一个按钮,一个label标签。点击按钮就可以打开窗体2。

窗体2有一个按钮,一个Txtbox文本框。点按钮就可以将窗体2中文本框的文本显示到窗体1中的标签label中。

一个场景有方法,没数据;另一个窗口有数据,没方法。

private void btnfm1_Click(object sender, EventArgs e)
{
    Form2 fm = new Form2();
    fm.Show();
}

为了实现文本消息的传递,我们在窗体1中类内,将值传递给lable1,我们写一个方法:

public void ShowMsg(string str)
{
    label1.Text = str;
}

这个str其实就是form2中的文本框的内容消息。

分析一下,目前的境地:窗体1也就是说Form1类中有方法把值赋值给label1,也就是说显示,但是他没有值(有方法无数据)。窗体2也就是说Form2类中有要传递的值(有数据无方法),但是没有传递的方法,方法在窗体1中Form1类中。

我们需要在Form2类中拿到Form1类中的方法就好了。谁可以把方法给传递过去。引出了委托。

思路:

  • 在Form2中使用Form1中的方法,所以这个方法作为参数从Form1中传入。
  • 这个委托变量在Form1中赋值,然后在Form2中真正被调用。
  • 如何实现传递,用委托。在哪里传递的。可以在Form1中构造Form2时,将方法从Form1传递给Form2。
  • 也就是在Form2构造函数时,需要Form1传递一个方法过去。
  • 所以委托在Form2的构造函数中作为一个参数占位,同时,将委托声明在Form2模块中。

声明一个委托,委托最终要指向我们Form1中的ShowMsg函数。被指向的函数签名:无返回值,有参数string。在Form2类中:

public delegate void DelShowMsg(string str);

怎么把方法传递过来?

方法在Form1中,委托在Form2中。怎么从Form1传递到Form2中。我们回看代码:在form1中,我们写了一个点击按钮初始化form2的方法,如果我们在初始化form2时可以把委托传进去就好了。构造函数可以做这事。new Form2(ShowMsg)。;这样Form2中既有方法又有值了。就能完成跨窗体传值这事。

(大数据思维:算法找数据。)

为此我们需要重写Form2中的构造函数。在form2的构造函数中也需要一个委托类型的字段来接收传递过来的委托函数。Form2中,我们声明一个委托类型的字段。

public DelShowMsg _del;

 传递过来的值赋值给字段。

namespace _02窗体传值
{
    public delegate void DelShowMsg(string str);

    public partial class Form2 : Form
    {
        public DelShowMsg _del;
        // 构造函数:构造Form2窗体时,把要在窗体中执行显示的函数方法传递过来。算法找数据。
        // Form2创建时,需要一个方法传递过来。所以构造函数前要有一个委托。
        // Form2中应该有个字段,接受这个委托方法,便于后面实现。
        public Form2(DelShowMsg del)
        {
            this._del = del;
            InitializeComponent();
        }
    }
}

通过委托把函数传递过来了,我们怎么用?

窗体2拿到了方法,当点击发送按钮之后,就应该把值传递给Form1。form2中可以访问到form1中的这个方法,这个方法就在_del中。直接执行就好了。

Form2中的字段_del,就是Form1派过去的关于方法ShowMsg的代表。_del在Form2中行使Form1中ShowMsg的权力,执行其功能。

private void btnfm2_Click(object sender, EventArgs e)
{
    _del(textBox1.Text);
}

 form1中初始化Form2时,传递函数方法ShowMsg。

全码如下:

namespace _02窗体传值
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnfm1_Click(object sender, EventArgs e)
        {
            Form2 fm = new Form2(ShowMsg);
            fm.Show();
        }

        public void ShowMsg(string str)
        {
            label1.Text = str;
        }
    }
}
namespace _02窗体传值
{
    public delegate void DelShowMsg(string str);

    public partial class Form2 : Form
    {
        public DelShowMsg _del;
        // 构造函数:构造Form2窗体时,把要在窗体中执行显示的函数方法传递过来。算法找数据。
        // Form2创建时,需要一个方法传递过来。所以构造函数前要有一个委托。
        // Form2中应该有个字段,接受这个委托方法,便于后面实现。
        public Form2(DelShowMsg del)
        {
            this._del = del;
            InitializeComponent();
        }

        private void btnfm2_Click(object sender, EventArgs e)
        {
            _del(textBox1.Text);
        }
    }
}


泛型委托

场景:获取任意数组的最大值。

核心代码:if(max<names[i]){max = name[i]}也就是说max减去names[i]小于0。则赋值。比较函数委托。

namespace _11_泛型委托
{
    public delegate int DelCompare<T>(T t1, T t2);
    // public delegate int DelCompare(object o1, object o2);
    class Program
    {
        static void Main(string[] args)
        {
            int[] nums = { 1, 2, 3, 4, 5 };
            int max = GetMax<int>(nums, Compare1);
            Console.WriteLine(max);

            string[] names = { "abcdefg", "fdsfds", "fdsfdsfdsfdsfdsfdsfdsfsd" };
            string max1 = GetMax<string>(names, (string s1, string s2) =>
            {
                return s1.Length - s2.Length;
            });
            Console.WriteLine(max1);
            Console.ReadKey();
        }
        public static T GetMax<T>(T[] nums, DelCompare<T> del)
        {
            T max = nums[0];
            for (int i = 0; i < nums.Length; i++)
            {
                //要传一个比较的方法
                if (del(max, nums[i]) < 0)
                {
                    max = nums[i];
                }
            }
            return max;
        }


        public static int Compare1(int n1, int n2)
        {
            return n1 - n2;
        }
    }
}

所谓的泛型:就是<T>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值