C#学习之步步高(二)认识到熟悉委托

大家好,这是本系列的第二篇文章,今天我给大家带来的是C#当中委托部分。

那么先讲讲委托吧,委托是一种定义方法签名的类型,当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联。 您可以通过委托实例调用方法。

使用委托的一个好处就是像操作变量一样操作一个方法。

下面是委托的一些特点:

  • 委托类似于 C++ 函数指针,但它们是类型安全的。
  • 委托允许将方法作为参数进行传递。
  • 委托可用于定义回调方法。
  • 委托可以链接在一起;例如,可以对一个事件调用多个方法。
  • 方法不必与委托签名完全匹配。
    下面 是定义个名为MyDelegate的委托,其参数类型为object,返回值为void
        public delegate void MyDelegate(object obj);

这行代码就是用来声明一个委托类型。再看如下代码:

    class Program
    {
        public delegate void MyDelegate(object obj);

        static void Main(string[] args)
        {
            // 在这儿,把MyDelegate看做一个类(其实它就是一个类class)
            // 下面这行代码和声明一个对象一模一样,因为mydelegate它就是一个对象
            // 注意Method1的原型和MyDelegte一致才行(逆变和协变的内容在后面)
            // 其实下面这行代码可以简写为:MyDelegate mydelegate = Method1;
            MyDelegate mydelegate = new MyDelegate(Method1);

            // 下面这行代码和mydelegate.Invoke(new object());没有任何区别!
            // 这行代码的目的就是执行Method1方法
            mydelegate(new object());
        }

        // 方法 i
        public static void Method1(object o)
        {
            Console.WriteLine("Method1 Calling");
        }
    }

运行输入结果:Method1 Calling.

那么委托时如何像一个参数一样传给别的方法呢?下面这段代码用委托回调的方式开求1~100的和:

    class Program
    {
        public delegate void CallBack(int number);

        static void Main(string[] args)
        {
            // 这个函数调用后,将输出1~100的和
            Execute(100, Sum);
        }

        private static void Execute(int param, CallBack callBack)
        {
            if (callBack != null)
            {
                // 在这个地方调用callBack委托
                callBack(param);
            }
        }

        private static void Sum(int number)
        {
            int sum = 0;
            for (int i = 0; i <= number; i++)
            {
                sum += i;
            }

            // 为了做演示,就暂且用这种最复杂的方法求和吧
            Console.WriteLine("1~" + number + "的和为" + sum);
        }
    }

程序运行结果:1~100的和为5050。

那么就有人要说了,我像调用一次Execute方法,求出1~100的和之外还求出他们的所有质数之和怎么做呢,那么就可以使用委托将多个方法链接到一起了:

    class Program
    {
        public delegate void CallBack(int number);

        static void Main(string[] args)
        {
            // 这个函数调用后,将输出1~100的和还有1~100之间质数的和
            CallBack linkDelegate = Sum;

            // 使用+=运算符,将2个方法链接一起
            linkDelegate += SumOfPrimeNumber;

            Execute(100, linkDelegate);
        }

        private static void Execute(int param, CallBack callBack)
        {
            if (callBack != null)
            {
                // 在这个地方调用callBack委托
                callBack(param);
            }
        }

        private static void Sum(int number)
        {
            int sum = 0;
            for (int i = 0; i <= number; i++)
            {
                sum += i;
            }

            // 为了做演示,就暂且用这种最复杂的方法求和吧
            Console.WriteLine("1~" + number + "的和为" + sum);
        }

        private static void SumOfPrimeNumber(int number)
        {
            int sum = 0;
            for (int i = 0; i <= number; i++)
            {
                if (IsPrime(i))
                {
                    sum += i;
                }
            }

            Console.WriteLine("1~" + number + "之间的所有质数的和为" + sum);
        }

        // 判断一个数字是否为质数
        private static bool IsPrime(int n)
        {
            for (int i = 2; i * i <= n; i++)
            {
                if (n % i == 0)
                {
                    return false;
                }
            }

            return true;
        }
    }

这段代码输出的结果为:1~100的和为5050。1~100之间的所有质数的和为1061。

下面就来讲讲委托的2个重点:逆变和协变。

  • 逆变:在使用委托中,可以将方法参数从他基类修改为他的派生类。
  • 协变:在使用委托中,可以将方法返回值从他派生类修改为他的基类。

下面是一个逆变的例子:

    class Program
    {
        public delegate void Contravariance(string str);

        static void Main(string[] args)
        {
            Contravariance md = Method1;

            md("Contravariance");
        }

        static void Method1(object obj)
        {
            Console.WriteLine(obj + " Method1 Calling");
        }
    }

尽管Method1的原型和Contravariance委托定义的不一致,但是对于参数而言,Method1中的object是Contravariance中的string的基类,所以上述代码还是能正常运行,这个就是逆变。

不过得注意下面这个例子:

    class Program
    {
        public delegate void Contravariance(int str);

        static void Main(string[] args)
        {
            // 注意下面这行代码,虽然int是object的派生类型,但是下面这行代码还是会导致编译不过,因为逆变和协变对值类型不起作用(int为值类型)
            Contravariance md = Method1;

            md(4);
        }

        static void Method1(object obj)
        {
            Console.WriteLine(obj + " Method1 Calling");
        }
    }

上述代码是无法通过编译的,在使用逆变和协变中是都不支持值类型的。

同理,下面这个是逆变的例子:

    class Program
    {
        public delegate object Convariance();

        static void Main(string[] args)
        {
            // 虽然Convariance委托定义的类型返回值和Method1不一致,但是这段代码是可以正常运行的
            Convariance md = Method1;

            md();
        }

        static string Method1()
        {
            return "Method1";
        }
    }

我们看一看如下代码:

    public delegate void TryCode(object userData);
    public delegate void WaitCallback(object state);
    public delegate void TimerCallback(object state);
    public delegate void ParameterizedThreadStart(object obj);

发现这些委托定义的共同点了吗?是不是都一模一样?事实上在.NET Framework上可以使用委托泛型,上面所有的泛型其实都可以仅使用:

    public delegate void Action<in T>(T obj);

这一个委托定义来实现就可以了。在System命名空间下有这种多达17个Action委托(这儿使用in关键字表示委托类型支持逆变):

    public delegate void Action();
    public delegate void Action<in T1>(T1 arg1);
    public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
    ...

如果是需要有参数的委托的话,可以使用如下委托:

    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

同样的System命名空间下也有17个Func的委托(使用out关键字表示委托泛型支持协变):

    public delegate TResult Func<out TResult>();
    public delegate TResult Func<in T1, out TResult>(T1 arg1);
    public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
    ...

最后是文本的关键点,为大家揭秘委托。

我们再看如下一行代码:

    internal delegate void Feedback(int value);

其实它被编译后,看起来像如下一个完整的类:

    internal class Feedback : System.MulticastDelegate
    {
        // 构造方法
        public Feedback(object obj, IntPtr method);

        // 这个方法的原型和委托定义的一模一样
        public virtual void Invoke(int value);

        // 一下方法实现了对回调方法的异步调用
        public virtual IAsyncResult BeginInvoke(int value, AsyncCallback callback, Object obj);
        public virtual void EndInvoke(IAsyncResult result);
    }

也就是说其实委托它本身也是一个类,其继承关系为:自定义委托->MulticastDelegate->Delegate->Object。

本文内容就讲到这里了,谢谢各位。

时间: 2024-08-09 06:22:16

C#学习之步步高(二)认识到熟悉委托的相关文章

Ext JS学习第三天 我们所熟悉的javascript(二)

•javascript之函数 •对于Ext开发者,我还是希望你能对javascript原生的东西非常了解.甚至熟练掌握运用.那么函数,无疑是非常重要的概念.首先在前面一讲,我们知道了函数也是一种数据类型,创建函数一共有三种方式.每种方式他们都会有区别,分别为: –function语句形式 –函数直接量形式 –构造函数形式 •函数中的arguments对象 –argument的作用一:接受函数的实际参数 –argument的作用二:用于做递归操作 栗子代码 1 //Function 函数 2 //

.NET Remoting学习笔记(二)激活方式

目录 .NET Remoting学习笔记(一)概念 .NET Remoting学习笔记(二)激活方式 参考:百度百科  ♂风车车.Net 激活方式概念 在访问远程类型的一个对象实例之前,必须通过一个名为Activation的进程创建它并进行初始化.这种客户端通过通道来创建远程对象,称为对象的激活. 激活分为两大类:服务器端激活  客户端激活 服务器端激活 又称WellKnow(知名对象) 服务器应用程序在激活对象实例之前会在一个众所周知的统一资源标识符(URI)上来发布这个类型.然后该服务器进程

VSTO学习笔记(二)Excel对象模型

原文:VSTO学习笔记(二)Excel对象模型 上一次主要学习了VSTO的发展历史及其历代版本的新特性,概述了VSTO对开发人员的帮助和效率提升.从这次开始,将从VSTO 4.0开始,逐一探讨VSTO开发中方方面面,本人接触VSTO时间不长,也是一次尝试.鉴于Excel在整个Office家族中的重要地位,故先从Excel开始介绍,后续内容会陆续介绍Word.PowerPoint.Outlook.InfoPath等.由于VSTO 4.0建立在Office 2010基础之上,先介绍一下Office

winform学习日志(二十三)---------------socket(TCP)发送文件

一:由于在上一个随笔的基础之上拓展的所以直接上代码,客户端: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Net.Sockets; using Sys

Unix文件系统学习笔记之二: 文件描述符、inode和打开文件表

Unix文件系统学习笔记之二: 文件描述符.inode和打开文件表 系统盘上数据的布局 文件系统无非是关于数据在磁盘上的组织以及存储空间管理的,为此,首先需要知道磁盘上数据的总体布局方式.以Unix为例,最重要的一张表如下: Unix 进程管理中和用户文件.io 最相关的数据结构:usr 数据结构 The procstructure does not record information related to file access.  However the userstructure con

C++primer学习笔记(二)——Chapter 4

4.1  Fundamentals 1.Basic Concepts (1)操作符分为一元,二元或者三元操作符: (2)复杂的表达式中含有很多操作符时: 规则一:分为不同的级别,级别高的先运行: 规则二:相同级别的操作符有执行顺序的确定: (3)操作符可以改变操作数的类型 一般将级别低的转化成级别高的 (4)重载运算符 相同的运算符在对不同类型的对象进行操作的时候,会有不同的功能: (5)Lvalue和Rvalue 显而易见:Lvalue指的是Left value,Rvalue指的是Right

Android学习路线(二十)运用Fragment构建动态UI

要在Android系统上创建一个动态或者多面板的用户界面,你需要将UI组件以及activity行为封装成模块,让它能够在你的activity中灵活地切换显示与隐藏.你可以使用Fragment类来创建这些模块,它们能够表现得有些像嵌套的activity,它们定义着自己的布局,管理自己的生命周期. 当一个fragment指定了它自己的布局,它可以在activity中和其他的fragment配置为不同的组合,这样就能够为不同的屏幕尺寸来修改你的布局配置(在小屏幕上一次展现一个fragment,而在大屏

Android学习路线(二十一)运用Fragment构建动态UI——创建一个Fragment

你可以把fragment看成是activity的模块化部分,它拥有自己的生命周期,接受它自己的输入事件,你可以在activity运行时添加或者删除它(有点像是一个"子activity",你可以在不同的activity中重用它).本课将向你展示如何使用Support Libaray继承 Fragment 类来让你的应用能够兼容正在运行Android 1.6的设备. 提示: 如果你决定你的应用需求的最低API级别是11或者更高,那么你不需要使用Support Library,你可以直接使用

struts2学习笔记(二)—— 获取登录信息及计算在线人数

实现目的: 1.点击"Login"的超链接,进入登录页面 2.填写登录信息,提交表单,将用户信息保存进Session 3.显示用户名,并计算在线人数 4.点击"Logout"的超链接,在线人数减一,并使Session失效 Struts2实现: 1.配置web.xml文件 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http:/

《语义网基础教程》学习笔记(二)

二.RDF概述(参考http://zh.transwiki.org/cn/rdfprimer.htm) 1.本体: 一个本体是一个概念体系(conceptualization)的显式的形式化规范. 一般来说,一个本体形式地刻画一个论域.一个典型的本体由有限个术语及它们之间的关系组成. ★在万维网这个环境中,本体提供了对给定领域的一种共识.这种共识对于消除术语差别是必要的. 通过把各自的术语差异映射到一个公共的本体之间的直接映射,可以消除这些术语差异. 不管采用哪种方案,本体都支持语义可共用性(s