深圳.net培训
达内深圳罗湖中心

186-8884-0703

热门课程

【达内.NET教程】.NET 委托类型解析

  • 时间:2016-07-01
  • 发布:达内
  • 来源:达内

不像Windows API中使用C语言风格的函数指针这种不安全的方式进行回调。.Net中此功能使用使用更为安全和面向对象的委托(delegate)来完成。委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。今天,深圳达内.NET培训(sz.net.tedu.cn)专家为大家详细讲解.NET 委托类型,希望能对你有所帮助。

委托类型包含3个重要信息:

  • 它所调用的方法的名称

  • 该方法的参数(可选)

  • 该方法的返回值(可选)

当上述信息被提供后,委托可以在运行时动态调用其指向的方法。很重要的一点:.Net中每个委托都被自动赋予同步或异步访问方法的能力。

定义委托

在C#中使用delegate关键字创建一个委托。我们称这种类为委托类。委托类的实例成为委托对象。从概念上说,委托对象是一种指向一个或多个方法(静态或非静态)的引用。要求是此委托匹配它指向的方法的签名。

如下委托可以指向一任何传入两个整数返回一个整数的方法。

public delegate int BinaryOp(int x, int y);

  • 定义委托后,系统生成一个派生自MulticastDelegate类的密封类。此类中有3个方法:

  • Invoke()方法,用来以同步方式调用委托维护的每个方法。(不能在C#中显示调用此方法,Invoke()在后台被调用)

  • BeginInvoke()与EndInvoke()方法在第二个线程上异步调用当前方法。

开发人员创建第二个执行线程的原因调用比较耗时的方法。(相当于委托顺带实现了一些System.Threading命名空间管理的线程问题)

这个委托的密封类大概如下:

sealed class BinaryOp : System.MulticastDelegate

{

      public BinaryOp(object target, uint functionAddress);

      public int Invoke(int x, int y);

      public IAsyncResult BeginInvoke(int x, int y, AsyncCallback cb, object state);

      public int EndInvoke(IAsyncResult result);

}

下面给一个简单的委托示例:

// 这个委托指向任何一个传入两个整数并返回一个整数的方法

    public delegate int BinaryOp(int x, int y);

 

    #region SimpleMath class

    public class SimpleMath

    {

        public int Add(int x, int y)

        { return x + y; }

        public int Subtract(int x, int y)

        { return x - y; }

        public static int SquareNumber(int a)

        { return a * a; }

    }

    

    #endregion

    

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("***** Simple Delegate Example *****n");

            // 创建一个指向SimpleMath.Add()方法的BinaryOp对象

            SimpleMath m = new SimpleMath();

            BinaryOp b = new BinaryOp(m.Add);

 

            // 使用委托调用调用Add()方法

            // 此处也是Invoke()被调用的位置

            Console.WriteLine("n10 + 10 is {0}", b(10, 10));

            Console.ReadLine();

        }

    }

委托类型安全的体现

如果传入一个与委托声明不匹配的方法,将在编译时报错。如上例中如果传入int SquareNumber(int),将会导致一个编译时错误。

获取委托中调用函数列表的方法,示例:

假设有名为delObj的委托对象,使用如下方式得到调用函数的信息

Delegate d in delObj.GetInvocationList()

    Console.WriteLine("Method Name: {0}", d.Method);

    Console.WriteLine("Target Name: {0}", d.Target);

Method属性表示调用的函数的签名,Target表示调用的函数所在的对象的类型名,所以如果委托调用的是一个静态方法则Target不会有任何显示,只有当委托调用的是一个实例方法时,Target属性才有值。

更完整的委托应用(示例来自C#与.Net3.0高级程序设计),代码:

汽车类:

using System;

using System.Collections.Generic;

using System.Text;

 

namespace CarDelegate

{

    public class Car

    {

        // 定义委托类型

        public delegate void AboutToBlow(string msg);

        public delegate void Exploded (string msg);

 

        // 定义各自委托类型的对象

        private AboutToBlow almostDeadList;

        private Exploded explodedList;

 

        // 将成员添加到调用列表

        public void OnAboutToBlow(AboutToBlow clientMethod)

        { almostDeadList += clientMethod; }

 

        public void OnExploded(Exploded clientMethod)

        { explodedList += clientMethod; }

 

        // 由调用列表移除方法

        public void RemoveAboutToBlow(AboutToBlow clientMethod)

        { almostDeadList -= clientMethod; }

 

        public void RemoveExploded(Exploded clientMethod)

        { explodedList -= clientMethod; }

 

        // 内部状态成员

        private int currSpeed;

        private int maxSpeed;

        private string petName;

 

        // 汽车坏了吗?

        bool carIsDead;

 

        public Car()

        {

            maxSpeed = 100;

        }

 

        public Car(string name, int max, int curr)

        {

            currSpeed = curr;

            maxSpeed = max;

            petName = name;

        }

 

        public void SpeedUp(int delta)

        {

            // 如果汽车坏了,触发Exploded事件

            if (carIsDead)

            {

                if (explodedList != null)

                    explodedList("Sorry, this car is dead");

            }

            else

            {

                currSpeed += delta;

 

                // 几乎要坏了?

                if (10 == maxSpeed - currSpeed

                    && almostDeadList != null)

                {

                    almostDeadList("Careful buddy!  Gonna blow!");

                }

 

                // 还好!

                if (currSpeed >= maxSpeed)

                    carIsDead = true;

                else

                    Console.WriteLine("->CurrSpeed = {0}", currSpeed);

            }

        }

    }

}

主函数:

namespace CarDelegate

{

    class Program

    {

        static void Main(string[] args)

        {

            // 制造一辆车

            Car c1 = new Car("SlugBug", 100, 10);

 

            // 注册事件处理函数          

            Car.Exploded d = new Car.Exploded(CarExploded);

            c1.OnAboutToBlow(new Car.AboutToBlow(CarIsAlmostDoomed));

            c1.OnAboutToBlow(new Car.AboutToBlow(CarAboutToBlow));

            c1.OnExploded(d);

 

            // 加速 (这将触发事件)

            Console.WriteLine("n***** 加速 *****");

            for (int i = 0; i < 6; i++)

                c1.SpeedUp(20);

 

            // 由调用列表移除CarExploded方法

            c1.RemoveExploded(d);

 

            Console.WriteLine("n***** 加速 *****");

            for (int i = 0; i < 6; i++)

                c1.SpeedUp(20);

 

            Console.ReadLine();

        }

 

        public static void CarAboutToBlow(string msg)

        { Console.WriteLine(msg); }

 

        public static void CarIsAlmostDoomed(string msg)

        { Console.WriteLine("Critical Message from Car: {0}", msg); }

 

        public static void CarExploded(string msg)

        { Console.WriteLine(msg); }

    }

}

对多路广播的支持

.Net委托内置多路,即一个委托可以维护一个可调用方法的列表而不只是单独一个方法,使用重载过的+=运算符可以向一个委托对象添加多个方法。关于对对路广播的支持可以参考上述示例。

在多路广播的支持中有一个需要注意的问题,一个委托调用的多个方法需要无参数且无返回值,因为在调用委托时,即使传入了参数也不知道具体应该传给哪一个方法,即使这些方法有返回值也不知道该接受那个函数的返回值。所以说直接不要调用有参数及返回值的方法,这点与事件关联多个事件处理方法时对处理方法签名的要求相同(可以参见本系列介绍事件的文章)。

注意:我们可以用调用方法的语法”调用”委托对象。这样会调用委托对象所引用的方法。(事件的触发与委托的调用相同,本来事件就是一个委托类型的对象)。这些方法的调用是在调用委托的方法所在的线程中完成的。这种调用称同步调用。

C#2.0编译器的委托类推测功能

C#编译器引入了在创建委托变量时可以推测其类型的能力。这样就可以将一个方法赋给隐式创建的委托对象。

示例:

public class Program {

  delegate void Deleg1();

  delegate string Deleg2( string s );

  static void f1() {

    System.Console.WriteLine("f1() called.");

  }

  static string f2(string s) {

    string _s=string.Format( "f2() called with the param "{0}"." , s );

    System.Console.WriteLine( _s );

    return _s;

  }

  public static void Main() {

     Deleg1 d1 = f1; // 代替 Deleg1 d1 = new Deleg1( f1 );

     d1();

     Deleg2 d2 = f2; // 代替 Deleg2 d2 = new Deleg2( f2 );

     string s = d2("hello");

  }

}

更多.NET培训、.NET就业、.NET薪资、.NET教程等内容,请访问达内深圳.NET培训官方网站!众多资深.NET大神级讲师为您答疑解惑!

上一篇:【达内.NET课堂】.NET开发者必备的免费工具
下一篇:【达内.NET教程】.NET开发常会忽略的几个错误

【达内.NET教程】必须会的10项.NET技术

【达内职场秀】.NET面试重点知识

【达内.NET教程】使用C#和.NET的原因

【达内.NET教程】.NET开发常会忽略的几个错误

选择城市和中心
贵州省

广西省

海南省

台湾