zoukankan      html  css  js  c++  java
  • 【C#系列】你应该知道的委托和事件

    本篇文章更适合具有一定开发经验,一定功底,且对底层代码有所研究的朋友!!!

    本篇文章主要采用理论和代码实例相结合方式来论述委托和事件,涉及到一些边界技术,如软件架构的OCP原则(开-闭原则),

    软件架构解耦,设计模式(Sender-Order)和事件驱动模型,有一定难度和深度,不适合初级者。

    第一部份   委托

    关于委托内容,主要围绕下图来论述。

     一   委托是什么(what)

    (一)委托产生的背景之一

    1.我们先来假设这样一个情景需求:

       设计一个系统,使其满足如下条件:

       (1)当前,只有中国人和英国人使用该系统;

       (2)向系统输入用户名和相应的语言,将产生相应语言的问候语;

          

      (3)后期,可能会有其他国家语言加入该系统(系统变化的部分) ;

     2.技术方案实现

    关于技术方案实现,我们可以采用下图中的三种方式之一。

    为了更好地叙述委托,我们分别实现三种技术方案,并找出它们的关系。

     2.1 一般实现

    Code(控制台程序)

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             Console.WriteLine(GetGreetingContens("小王", "Chinese"));
    10             Console.WriteLine(GetGreetingContens("Alan_beijing", "English"));
    11             Console.WriteLine(GetGreetingContens("Linda", "Russian"));
    12             Console.Read();
    13         }
    14 
    15         //根据用户名和语言,获取问候语
    16         public static string GetGreetingContens(string UserName, string Language)
    17         {
    18             //New 一个GreetToUsers对象
    19             GreetToUsers greetToUsers = new GreetToUsers();
    20             //当然,你也可以使用switch开发语句来代替如下的if......else......
    21             if (Language == "Chinese")
    22             {
    23                 return greetToUsers.ChinesePeople(UserName);
    24             }
    25             else if (Language == "English")
    26             {
    27                 return greetToUsers.EnglishPeople(UserName);
    28             }
    29             else
    30             {
    31                 return "抱歉,当前系统只支持汉语与英语(Sorry, the current system only supports Chinese and English.)";
    32             }
    33         }
    34     }
    35 
    36 
    37 
    38     //定义基本问候类和方法
    39     public class GreetToUsers
    40     {
    41         //Chinese People
    42         public string ChinesePeople(string UserName)
    43         {
    44             string GreetContents = "您好!" + UserName;
    45             return GreetContents;
    46         }
    47 
    48         //English People
    49         public string EnglishPeople(string UserName)
    50         {
    51             string GreetContents = "Hello," + UserName + "!";
    52             return GreetContents;
    53         }
    54     }
    55 
    56 }
    View Code

     Result

    分析

     

    2.2用接口实现

    如上,我们分析了方案一中的问题,为了更好地解决方案一存在的问题,我们采用面向接口编程的形式来实现。

    2.2.1  什么是面向接口编程?

    面向接口编程,主要是解决软件架构设计中“动静问题”,即封装不变(静),剥离变化(抽出变化)。

     Code:

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             GreetToChineseUsers greetToChinesUsers = new GreetToChineseUsers();
    10             GreetToEnglishUsers greetToEnglishUsers = new GreetToEnglishUsers();
    11             GreetToOtherUsers greetToOtherUsers = new GreetToOtherUsers();
    12             //Chinse Users
    13             IGreetToUsers iGreetToChineseUsers = greetToChinesUsers;
    14             Console.WriteLine(iGreetToChineseUsers.CountryPeople("小王", "Chinese"));
    15             //English Users
    16             IGreetToUsers iGreetToEnglishUsers = greetToEnglishUsers;
    17             Console.WriteLine(iGreetToEnglishUsers.CountryPeople("Alan_beijing", "English"));
    18             //Other Users
    19             IGreetToUsers iGreetToOtherUsers = greetToOtherUsers;
    20             Console.WriteLine(iGreetToOtherUsers.CountryPeople("Linda", "Russian"));
    21              
    22             Console.Read();
    23         }
    24 
    25         
    26     }
    27 
    28     //系统输出问候语(变化的部分,语言为变化因子)
    29     public interface IGreetToUsers 
    30     {
    31         string CountryPeople(string UserName,string Language);
    32     }
    33 
    34 
    35     //汉语用户类
    36     public class GreetToChineseUsers:IGreetToUsers
    37     {
    38         //Chinese People
    39         public string  CountryPeople(string UserName, string Language)
    40         {
    41             string GreetContents = "您好!" + UserName;
    42             return GreetContents;
    43         }
    44     }
    45     
    46     //英语用户类
    47     public class GreetToEnglishUsers : IGreetToUsers
    48     {
    49         //English People
    50         public string CountryPeople(string UserName, string Language)
    51         {
    52             string GreetContents = "Hello," + UserName + "!";
    53             return GreetContents;
    54         }
    55     }
    56 
    57     //其他用户类
    58     public class GreetToOtherUsers : IGreetToUsers
    59     {
    60         //English People
    61         public string CountryPeople(string UserName, string Language)
    62         {
    63             return "Sorrry,当前系统只支持汉语与英语";
    64         }
    65     }
    66 
    67 }
    View Code

    result

    分析:

    (1)如上,我们将变化因子"语言"剥离出来,形成接口,以后只要每增加一个语言,只需实现接口即可,满足了OCP原则,基本解决了方案一中存在的问题;

    (2)如上代码只是为了演示面向接口编程这个功能,并不完善,感兴趣的读者,可自行完善(如将语言定义为枚举类型等);

    方案二中的代码,细心的读者会发现,Main方法中new了三个对象,假若以后系统有300门语言,那岂不New 300个类,这样的话,也不利于代码维护呀,怎么解决这个问题呢?(提示一下,采用设计模式抽象工厂即可解决该问题)

    2.3 用委托实现

    在这里,没接触过委托的读者,先跳过这部分,往下读,看完(三)怎样使用委托(How to use)后,再来看本部分。

     Code

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9             //根据语言判断,传递哪个方法的参数
    10             Console.WriteLine("------------请输入用户名------------");
    11             string UserName = Console.ReadLine();
    12             Console.WriteLine("------------请输入语言------------");
    13             string Language = Console.ReadLine();
    14             Console.WriteLine("------------输出结果------------");
    15             GreetToUsers greetToUsers = new GreetToUsers();
    16             if (Language == "Chinese")
    17             {
    18                Console.WriteLine(GetGreetingContents(UserName, greetToUsers.ChinesePeople));
    19             }
    20             else if (Language == "English")
    21             {
    22                 Console.WriteLine(GetGreetingContents(UserName, greetToUsers.EnglishPeople));
    23             }
    24             else
    25             {
    26                Console.WriteLine(GetGreetingContents(UserName, greetToUsers.OtherPeople));
    27             }
    28            Console.Read();
    29         }
    30         
    31         
    32 
    33         public static string GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting)
    34         {
    35            return delegateGetGreeting(UserName);
    36         }
    37     }
    38 
    39     //定义委托
    40     public delegate string DelegateGetGreeting(string UserName);
    41 
    42 
    43     //定义基本问候类和方法
    44     public  class GreetToUsers
    45     {
    46         //Chinese People
    47         public string ChinesePeople(string UserName)
    48         {
    49             string GreetContents = "您好!" + UserName;
    50             return GreetContents;
    51         }
    52 
    53         //English People
    54         public string EnglishPeople(string UserName)
    55         {
    56             string GreetContents = "Hello," + UserName + "!";
    57             return GreetContents;
    58         }
    59         //非英非汉
    60         public string OtherPeople(string UserName)
    61         {
    62             return "Sorrry,当前系统只支持汉语与英语";
    63         }
    64     }
    65 
    66 }
    View Code

     Result

     2.3 分析上述三种实现方案的关系

    通过上诉三种方式的比较,我们容易得出委托的如下结论:

    (1)抽象方法,屏蔽方法细节,调用只需传递方法名字即可;

    (2)能够实现程序的解耦,松耦合(在方案一中,GetGreetingContens方法体内new了GreetToUsers对象,强耦合)

    (3)委托一般扮演中间者的角色,这功能在委托事件中体现非常明显(第二部分 事件 将详细论述)

    如我们在租房子时,可以直接找房东(技术实现的一般方法,强耦合,让租房者和房东直接联系),也可找中介(技术实现的委托,松耦合,租房者通过中介来与房东联系)

     

    2.4 委托背景概述

    委托的重要产生背景,就是事件驱动模型(关于什么是事件和事件驱动,在本文第二部份 事件 论述)。

    (二) 委托定义

     用delegate关键字定义委托(注意,委托是没有方法体的,类似接口里面的方法),在定义委托前,必须明确两个问题:

    1.委托将要绑定的方法;

    2.委托的形参类型,形参个数和委托的返回值必须与将要绑定的方法的形参类型,形参个数和返回值一致;

    (三)相关概念

    委托涉及的相关概念有函数指针,类型安全性、事件、Lambda表达式等

    1.函数指针:在C++中,指针的一个类别,主要指向函数(变量指针,主要指向变量地址),可以把C#中的委托理解为函数指针;

    2.类型安全性:在C++中,我们都知道指针是类型不安全的(返回值,返回类型和什么时候返回,这些都是未知的),而委托是类型安全的;

    3.事件:可以把事件理解为委托的一种特例(在本文第二部份 事件 论述)

    4.Lambda表达式:委托与Lambd表达式相结合,实现高效编程,与Jquery的“较少代码做更多的事”类似,委托与Lambda,Linq相结合,使较短代码就能实现比较复杂的功能(在本篇文章中不讲解Lambda与Lambda树,将在后续文章中讲解)

    (四)委托组成

    大致分为两部分:声明委托和注册方法(也叫绑定方法)

    1.声明委托

    用delegate声明;

    2.绑定方法

    绑定具体方法,传递方法名称;

    (五) 委托种类

    委托种类,一般分为多播委托和单播委托

    1.单播委托:绑定单个方法

    2.绑定多个方法

    (六) 委托操作

    1.绑定方法

    2.解绑方法

    二  委托能解决什么问题(Can do)

    1.避免核心方法中存在大量的if....else....语句(或swich开关语句);

    2.满足程序设计的OCP原则;

    3.使程序具有扩展性;

    4.绑定事件;

    5.结合Lambda表达式,简化代码,高效编程;

    6.实现程序的松耦合(解耦),这个在事件(event)中体现比较明显;

    三  怎么使用委托(How to use)(本篇文章不谈匿名委托,匿名委托具体内容,将在Lambda章节讲解)

    (一)委托的基本构成

    通常地,使用委托的步骤与使用类的步骤是一样的。大致分为两步:定义委托和绑定方法(传递方法)

    1.定义委托

    用delegate关键字定义委托(注意,委托是没有方法体的,类似接口里面的方法),在定义委托前,必须明确两个问题:

    (1).委托将要绑定的方法;

    (2).委托的形参类型,形参个数和委托的返回值必须与将要绑定的方法的形参类型,形参个数和返回值一致;

    public delegate  委托返回类型  委托名(形参)

    例子:如上我们委托将要表示系统输出的问候语

    a.委托将要绑定的方法

     public string ChinesePeople(string UserName)
            {
                string GreetContents = "您好!" + UserName;
                return GreetContents;
            }
    
            //English People
            public string EnglishPeople(string UserName)
            {
                string GreetContents = "Hello," + UserName + "!";
                return GreetContents;
            }
            //非英非汉
            public string OtherPeople(string UserName)
            {
                return "Sorrry,当前系统只支持汉语与英语";
            }

    b.由如上方法可看出,方法的返回类型为string,方法有一个string类型的形参,在定义委托时,与其保持一致即可

    //定义委托
    public delegate string DelegateGetGreeting(string UserName);

    2.绑定方法

    使用委托时,将方法名字作为参数传递给委托即可

    1 GreetToUsers greetToUsers = new GreetToUsers();
    2 GetGreetingContents(UserName, greetToUsers.ChinesePeople)

    (二)委托绑定方法

    1.绑定单个方法

    绑定单个方法,将单个方法名字传给委托即可

     1 static void Main(string[] args)
     2         {
     3             //根据语言判断,传递哪个方法的参数
     4             Console.WriteLine("------------请输入用户名------------");
     5             string UserName = Console.ReadLine();
     6             Console.WriteLine("------------请输入语言------------");
     7             string Language = Console.ReadLine();
     8             Console.WriteLine("------------输出结果------------");
     9             GreetToUsers greetToUsers = new GreetToUsers();
    10             DelegateGetGreeting DGG;
    11             if (Language == "Chinese")
    12             {
    13                 //绑定单个方法
    14                 DGG = greetToUsers.ChinesePeople;
    15                 Console.WriteLine(GetGreetingContents(UserName, DGG));
    16             }
    17             else if (Language == "English")
    18             {
    19                 DGG = greetToUsers.EnglishPeople;
    20                 Console.WriteLine(GetGreetingContents(UserName, DGG));
    21             }
    22             else
    23             {
    24                 DGG = greetToUsers.OtherPeople;
    25                 Console.WriteLine(GetGreetingContents(UserName, DGG));
    26             }
    27             
    28             Console.Read();
    29         }
    View Code

    另一种不太规范写法:不用GetGreetingContents(string UserName,DelegateGetGreeting delegateGetGreeting)方法

     1 static void Main(string[] args)
     2         {
     3             //根据语言判断,传递哪个方法的参数
     4             Console.WriteLine("------------请输入用户名------------");
     5             string UserName = Console.ReadLine();
     6             Console.WriteLine("------------请输入语言------------");
     7             string Language = Console.ReadLine();
     8             Console.WriteLine("------------输出结果------------");
     9             GreetToUsers greetToUsers = new GreetToUsers();
    10             DelegateGetGreeting DGG;
    11             if (Language == "Chinese")
    12             {
    13                 //绑定单个方法
    14                 DGG = greetToUsers.ChinesePeople;
    15                 Console.WriteLine(DGG(UserName));
    16             }
    17             else if (Language == "English")
    18             {
    19                 DGG = greetToUsers.EnglishPeople;
    20                 Console.WriteLine(DGG(UserName));
    21             }
    22             else
    23             {
    24                 DGG = greetToUsers.OtherPeople;
    25                 Console.WriteLine(DGG(UserName));
    26             }
    27             
    28             Console.Read();
    29         }
    View Code

    之所以不规范,主要是在项目中,不利于代码的模块化。

    2.绑定多个方法(多播委托)

    注意:绑定多个方法时,委托范围类型必须为void类型,否则只返回最后一个绑定的值。

     绑定多个方法,采用 += 绑定

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9              
    10             GreetToUsers greetToUsers = new GreetToUsers();
    11             DelegateGetGreeting DGG;
    12             
    13             //绑定多个方法
    14             DGG = greetToUsers.ChinesePeople;
    15             DGG += greetToUsers.EnglishPeople;
    16             DGG("小王");
    17 
    18             Console.Read();
    19         }
    20         
    21         
    22     }
    23 
    24     //定义委托
    25     public delegate void DelegateGetGreeting(string UserName);
    26 
    27 
    28     //定义基本问候类和方法
    29     public  class GreetToUsers
    30     {
    31         //Chinese People
    32         public void ChinesePeople(string UserName)
    33         {
    34             string GreetContents = "您好!" + UserName;
    35             Console.WriteLine(GreetContents);
    36         }
    37 
    38         //English People
    39         public void EnglishPeople(string UserName)
    40         {
    41             string GreetContents = "Hello," + UserName + "!";
    42             Console.WriteLine(GreetContents);
    43         }
    44         //非英非汉
    45         public void OtherPeople(string UserName)
    46         {
    47             Console.WriteLine("Sorrry,当前系统只支持汉语与英语");
    48         }
    49     }
    50 
    51 }
    52    
    53 
    54  
    View Code

    3.解绑方法

    解载绑定的方法,采用 -= 解绑

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9              
    10             GreetToUsers greetToUsers = new GreetToUsers();
    11             DelegateGetGreeting DGG;
    12             
    13             //绑定多个方法
    14             DGG = greetToUsers.ChinesePeople;
    15             DGG += greetToUsers.EnglishPeople;
    16 
    17             //解绑ChinesePeople方法
    18             DGG-= greetToUsers.ChinesePeople;
    19             DGG("小王");
    20 
    21             Console.Read();
    22         }
    23         
    24         
    25     }
    26 
    27     //定义委托
    28     public delegate void DelegateGetGreeting(string UserName);
    29 
    30 
    31     //定义基本问候类和方法
    32     public  class GreetToUsers
    33     {
    34         //Chinese People
    35         public void ChinesePeople(string UserName)
    36         {
    37             string GreetContents = "您好!" + UserName;
    38             Console.WriteLine(GreetContents);
    39         }
    40 
    41         //English People
    42         public void EnglishPeople(string UserName)
    43         {
    44             string GreetContents = "Hello," + UserName + "!";
    45             Console.WriteLine(GreetContents);
    46         }
    47         //非英非汉
    48         public void OtherPeople(string UserName)
    49         {
    50             Console.WriteLine("Sorrry,当前系统只支持汉语与英语");
    51         }
    52     }
    53 
    54 }
    View Code

    (三)委托机制

     将如下代码通过反汇编工具.NET Reflector反汇编

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9 
    10             GreetToUsers GTU = new GreetToUsers();
    11 
    12             DelegateGreet DG = new DelegateGreet();
    13 
    14             //DG.delegateGetGreeting = GTU.ChinesePeople;//注册方法
    15             DG.delegateGetGreeting += GTU.ChinesePeople;
    16             DG.delegateGetGreeting += GTU.EnglishPeople;
    17             DG.GreetUser("小王");
    18 
    19             Console.Read();
    20         }
    21     }
    22 
    23     public class DelegateGreet
    24     {
    25         //声明委托
    26         public delegate void DelegateGetGreeting(string UserName);
    27 
    28         //委托变量为public,破坏了类的封装性
    29         public DelegateGetGreeting delegateGetGreeting;
    30 
    31         //虽然Event论定义为public,但其还是私有变量,只能通过+=,或-=访问
    32         //public event DelegateGetGreeting EventGreet;
    33 
    34         public void GreetUser(string UserName)
    35         {
    36             //EventGreet?.Invoke(UserName);
    37             delegateGetGreeting(UserName);
    38         }
    39     }
    40 
    41     //定义基本问候类和方法
    42     public class GreetToUsers
    43     {
    44         //Chinese People
    45         public void ChinesePeople(string UserName)
    46         {
    47             string GreetContents = "您好!" + UserName;
    48             Console.WriteLine(GreetContents);
    49         }
    50 
    51         //English People
    52         public void EnglishPeople(string UserName)
    53         {
    54             string GreetContents = "Hello," + UserName + "!";
    55             Console.WriteLine(GreetContents);
    56         }
    57         //非英非汉
    58         public void OtherPeople(string UserName)
    59         {
    60             Console.WriteLine("Sorrry,当前系统只支持汉语与英语");
    61         }
    62     }
    63 }
    64  
    65  
    View Code

    反汇编

     分析:

    1.三个核心方法:BeginInvoke,EndInvoke和Invoke

    (1)使用Invoke完成一个委托方法的封送,就类似于使用SendMessage方法来给界面线程发送消息,是一个同步方法。也就是说在Invoke封送的方法被执行完毕前,Invoke方法不会返回,从而调用者线程将被阻塞。

    (2使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage进行通信,这是一个异步方法。也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。但是调用者也

    可以使用EndInvoke方法或者其它类似WaitHandle机制等待异步操作的完成。

    总结:但是在内部实现上,Invoke和BeginInvoke都是用了PostMessage方法,从而避免了SendMessage带来的问题。而Invoke方法的同步阻塞是靠WaitHandle机制来完成的。

    提示:

    最近浏览一篇文章,也讲得不错:http://blog.csdn.net/goodshot/article/details/6157529

    要想深入了解,请参照《CLR Via C#》,

    第二部分  事件

    关于事件(event),将会从如下四个角度来分析.

    1.什么是事件

    2.事件能解决什么问题

    3.怎么使用事件

    4.事件机制

     

    一  什么是事件

     谈到委托,必提事件,事件本质是对委托的封装,对外提供add_EventName(对应+=)和remove_EventName(对应-=)访问,从而实现类的封装性。

    1.种类

    强类型事件和弱类型事件

    2.一些用处

    (1)WebForm控件的Click事件。做过WebForm开发的朋友,可能对事件是非常熟悉的,如拖一个Button,双击,就自动在后台生成Button的Click事件,如下图所示。

    原理:在Windows运用程序中,Button类提供了Click事件,其本质就是委托,当我们触发Click事件时,调用的处理程序方法需要参数,其参数就是由委托类型来定义的。

    (2)设计模式发布/订阅。事件是基于委托的,为委托提供了一种发布/订阅机制。

    二 事件能解决哪些问题

    1.将公有的委托变量定义为私有变量,从而满足类的封装性原则;

    2.具有委托具有的作用;

    三 如何使用事件

    1.声明委托

    public delegate void DelegateGetGreeting(string UserName);

    2.声明事件

    与委托声明一样,只不过多了一个关键字event

    public event DelegateGetGreeting EventGreet;

    3.时间注册方法

    事件注册方法与委托注册方法是一样的。

    1 DelegateGreet DG= new DelegateGreet();
    2 //DG.delegateGetGreeting = GTU.ChinesePeople;//注册方法
    3 DG.EventGreet+= GTU.ChinesePeople;
    4 DG.EventGreet += GTU.EnglishPeople;

    4.调用事件

    调用定义事件的方法

    DG.GreetUser("小王");

    完整代码如下:

     1 using System;
     2 
     3 namespace DelegateDemo
     4 {
     5     class Program
     6     {
     7         static void Main(string[] args)
     8         {
     9              
    10             GreetToUsers GTU = new GreetToUsers();
    11 
    12             DelegateGreet DG= new DelegateGreet();
    13 
    14             //DG.delegateGetGreeting = GTU.ChinesePeople;//注册方法
    15             DG.EventGreet+= GTU.ChinesePeople;
    16             DG.EventGreet += GTU.EnglishPeople;
    17             DG.GreetUser("小王");
    18 
    19             Console.Read();
    20 
    21         }
    22         
    23         
    24     }
    25     public class DelegateGreet
    26     {
    27         //声明委托
    28         public delegate void DelegateGetGreeting(string UserName);
    29 
    30         //委托变量为public,破坏了类的封装性
    31         //public DelegateGetGreeting delegateGetGreeting;
    32 
    33         //虽然Event论定义为public,但其还是私有变量,只能通过+=,或-=访问
    34         public event DelegateGetGreeting EventGreet;
    35 
    36         public void GreetUser(string UserName)
    37         {
    38             EventGreet?.Invoke(UserName);
    39             //delegateGetGreeting(UserName);
    40         }
    41 
    42 
    43     }
    44 
    45 
    46     //定义基本问候类和方法
    47     public  class GreetToUsers
    48     {
    49         //Chinese People
    50         public void ChinesePeople(string UserName)
    51         {
    52             string GreetContents = "您好!" + UserName;
    53             Console.WriteLine(GreetContents);
    54         }
    55 
    56         //English People
    57         public void EnglishPeople(string UserName)
    58         {
    59             string GreetContents = "Hello," + UserName + "!";
    60             Console.WriteLine(GreetContents);
    61         }
    62         //非英非汉
    63         public void OtherPeople(string UserName)
    64         {
    65             Console.WriteLine("Sorrry,当前系统只支持汉语与英语");
    66         }
    67     }
    68 
    69 }
    70    
    71 
    72  
    View Code

    四 事件机制

     事件的本质就是委托,向外提供两个访问方法add_EventName(对应+=)和remove-EventName(对应-=),我们通过.NET Reflector反汇编工具来查看,到底是不是这样的。

    参考文献

    【01】C#高级编程(第七版)  (Christian Nagel,Bill Evjen和Jay Glynn 编著,李铭 译,黄静 审校) 

    版权区

    • 感谢您的阅读,若有不足之处,欢迎指教,共同学习、共同进步。
    • 博主网址:http://www.cnblogs.com/wangjiming/。
    • 极少部分文章利用读书、参考、引用、抄袭、复制和粘贴等多种方式整合而成的,大部分为原创。
    • 如您喜欢,麻烦推荐一下;如您有新想法,欢迎提出,邮箱:2098469527@qq.com。
    • 可以转载该博客,但必须著名博客来源。
  • 相关阅读:
    js 生成随机数
    解决微信浏览器无法使用reload()刷新页面
    js 去除左右空格
    小程序开发入门-第一天
    我的第一个JSP——动态web
    2019-3-6 复制粘贴
    2019-2-19 异常练习
    2019-1-19 object祖宗类的equals重写
    2019-1-15 课堂笔记
    2019-1-15 课后作业
  • 原文地址:https://www.cnblogs.com/wangjiming/p/8300103.html
Copyright © 2011-2022 走看看