C#(二) C#高级进阶

1.C# 委托

​ C# 中的委托(Delegate)类似于 C 或 C++ 中的函数指针,是一种引用类型,表示对具有特定参数列表和返回值类型的方法的引用。简单来说,委托是一种方法的代理/中介,委托指向某个方法来实现具体的功能。委托是方法的抽象,它存储的就是一系列具有相同参数和返回值类型的方法的地址。 委托的使用包括定义声明委托、实例化委托以及调用委托三个阶段,在实例化委托时,必须将委托的实例与具有相同返回值类型以及参数列表的方法相关联,这样就可以通过委托来调用方法。另外,使用委托还可以将方法作为参数传递给其他方法。下面是一个委托的形象例子:

某人有三子,让他们各自带一样东西出门,并带回一头猎物。
上面一句话可以理解为父亲对儿子的委托:猎物 办法(工具 某工具)-->delegate 猎物(返回值) 带回猎物(委托名)(工具(参数类型) x)-->delegate int GetValue(int i)
三个人执行委托的方法各不相同
兔子 打猎(工具 弓)-public static int GetValue1(int i){ return i; }
野鸡 买(工具 钱)-public static int GetValue2(int i){ return i*2; }
狼 诱捕(工具 陷阱)-public static int GetValue3(int i){ return i*i; }

1.1 声明委托

delegate <return type> delegate-name(<parameter list>)
- return type 为返回值类型
- delegate-name 为委托的名称
- parameter list 为参数列表

注意: 委托可以引用与委托具有相同签名的方法,也就是说委托在声明时即确定了委托可以引用的方法。

public delegate int MyDelegate (string s);
//上面的委托可被用于引用任何一个带有一个单一的 string 参数的方法,并返回一个 int 类型变量。

1.2 实例化委托

​ 委托一旦声明,想要使用就必须使用 new 关键字来创建委托的对象,同时将其与特定的方法关联。当创建委托时,传递到 new 语句的参数为关联的方法名称,就像方法调用一样书写,但是不带有参数。

public delegate void printString(string s);                      // 声明一个委托
...
printString ps1 = new printString(WriteToScreen);       // 实例化委托对象并将其与 WriteToScreen 方法关联
printString ps2 = new printString(WriteToFile);            // 实例化委托对象并将其与 WriteToFile 方法关联

1.3 委托的使用

  • Action(arg): 调用委托Action并传入参数arg
  • Action.Invoke(arg): 调用委托Action并传入参数arg
  • 两种方式的关系:所有的委托类型,编译器都会自动生成一个 invoke 方法。用委托类型直接加参数是Invoke(参数)的一个捷径。其实等价调用 Invoke();
using System;

delegate int NumberChanger(int n);      // 定义委托
namespace c.biancheng.net
{
    
    
    class Demo
    {
    
    
        static int num = 10;
        public static int AddNum(int p){
    
    
            num += p;
            return num;
        }

        public static int MultNum(int q){
    
    
            num *= q;
            return num;
        }
        
        public static int getNum(){
    
    
            return num;
        }
        
        
        public static void Calculate(NumberChanger nc, int a)
        {
    
    
            nc(a);
        }
        
        static void Main(string[] args){
    
    
            // 创建委托实例
            NumberChanger nc1 = new NumberChanger(AddNum); 
            // 使用委托对象调用方法
            // 等价于 nc1.Invoke(25)
            nc1(25);
            Console.WriteLine("num 的值为: {0}", getNum());
            // 使用委托传递方法作为参数
            Calculate(MultNum,5)
            Console.WriteLine("num 的值为: {0}", getNum());
            Console.ReadKey();
        }
    }
}

1.4 多播委托(合并委托)

​ 在开发中,我们有时候会遇到要通过调用一个委托,同时可以执行多个方法的时候,就可以考虑用多播委托。委托对象有一个非常有用的属性,那就是可以通过使用+运算符将多个对象分配给一个委托实例,同时还可以使用-运算符从委托中移除已分配的对象,当委托被调用时会依次调用列表中的委托。委托的这个属性被称为委托的多播,也可称为组播,利用委托的这个属性,您可以创建一个调用委托时要调用的方法列表。注意:仅可合并类型相同的委托。

  • 多播委托的实质:一个委托实例不仅可以指向一个方法,还可以指向多个方法。

  • 总结:多播委托提供了一种类似于流水线的钩子机制,只要加载到这条流水线上的委托,都会被顺序执行。因为所有的都继承自MulticastDelegate,因此所有的委托都具有多播特性

using System;

delegate int NumberChanger(int n);      // 定义委托
namespace c.biancheng.net
{
    
    
    class Demo
    {
    
    
        static int num = 10;
        public static int AddNum(int p){
    
    
            num += p;
            return num;
        }

        public static int MultNum(int q){
    
    
            num *= q;
            return num;
        }
        public static int getNum(){
    
    
            return num;
        }
        static void Main(string[] args){
    
    
            // 创建委托实例
            NumberChanger nc;
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MultNum);
            nc = nc1;
            nc += nc2;
            // 调用多播
            nc(5);
            Console.WriteLine("num 的值为: {0}", getNum());
            Console.ReadKey();
        }
    }
}

1.5 Func与Action

​ 在上述我们在使用委托的过程中,都是自定义一个委托类型,再使用这个自定义的委托定义一个委托字段或变量。而在.NetFramework3.0之后又新加入了一种特性,C#语言预先为我们定义了两个常用的委托,分别是Func和Action,还带来了Lambda,这使得委托的定义和使用变得简单起来, 在以后进行C#程序编写中引入委托更加灵活。

(1)Action 委托

​ C#中与预定义了一个委托类型Action,其基本特点就是可以执行/指向一个没有返回值(void)的方法。是一类没有输出参数的委托。

  • 原生Action: public delegate void Action(); 封装一个方法,该方法不具有参数且不返回值。
  • 泛型Action: public delegate void Action(T arg1,…); 封装一个方法,该方法具有参数并且不返回值(最多可有16个参数)
    static void printString()
    {
    
    
        Console.WriteLine("Hello World");
    }
    static void printNumber(int x)
    {
    
    
        Console.WriteLine(x);
    }
    static void Main(String[] args)
    {
    
    
        //Action基本使用
        Action a = printString;
        a(); // 输出结果  Hello World
        //Action指向有参数的方法
        Action<int> b = printNumber; // 定义一个指向 形参为int的函C#数
        b(5); // 输出结果  5
    }

(2)Func 委托

​ 委托类型Func同样也是C#中与预定义的委托,其基本特点就是可以执行/指向一个具有返回值的方法。与Action不同的是Func只有带泛型的一种形式。

  • 无参 Func:public delegate TResult Func();封装一个方法,该方法不具有参数,且返回由 TResult 参数指定的类型的值。
  • 有参 Func:public delegate TResult Func<in T,int T,…,out TResult>(T arg1,T arg2,…);封装一个方法,该方法具有参数,且返回由 TResult 参数指定的类型的值。(最多可有16个参数)
  • 注意:泛型列表中最后一个泛型参数代表返回类型,前面的都是参数类型;参数类型必须跟指向的方法的参数类型按照顺序一一对应。
namespace delegatesample_10._15_
{
    
    
    class Program
    {
    
    
        static void Main(string[] args)
        {
    
    
            Calculator calculator = new Calculator();
            //声明Func
            Func<int, int, int> func1 = new Func<int, int, int>(calculator.Add);
            Func<int, int, int> func2 = new Func<int, int, int>(calculator.Sub);
            int x = 100;
            int y = 200;
            int z1 = 0;
            int z2 = 0;
            z1 = func1.Invoke(x, y);
            Console.WriteLine(z1);
            z1 = func1(x, y);
            Console.WriteLine(z1);//都是Add方法的调用,输出两遍。
            z2 = func2.Invoke(x, y);
            Console.WriteLine(z2);
            z2 = func2(x, y);
            Console.WriteLine(z2);//都是Sub方法的调用,输出两遍。
        }
    }
    class Calculator
    {
    
    
        public int Add(int a,int b)
        {
    
    
            int result = a + b;
            return result;
        }
        public int Sub(int a,int b)
        {
    
    
            int result = a - b;
            return result;
        }
    }
}

2.C# 类型转换

2.1 类型检查 is

  • is: 检查对象是否与给定类型兼容。
object objTest = 11;
if( objTest is int )
{
    
    
    int nValue = (int)objTest;
}

2.2 自动转换 as

  • as: 用于在兼容的引用类型之间自动执行转换。如果是,as会返回对同一个对象的一个非null的目标引用;如果不兼容于目标类型,as运算符会返回null。
Employee myEmployee = myObject as Employee; 
if (myEmployee != null) {
    
     
	...
}

注意: 用as来进行类型转换的时候,所要转换的对象类型必须是目标类型或者转换目标类型的派生类型,但不能应用在值类型数据。参考:C#中对象或普通类型进行类型转换

3.C# 匿名函数/方法

​ 在 C# 中,可以将匿名函数简单的理解为没有名称只有函数主体的函数。匿名函数提供了一种将代码块作为委托参数传递的技术,它是一个“内联”语句或表达式,可在任何需要委托类型的地方使用。匿名函数可以用来初始化命名委托或传递命名委托作为方法参数。需要注意的是:无需在匿名函数中指定返回类型,返回值类型是从方法体内的 return 语句推断出来的。匿名函数包括 Lambda 表达式和普通匿名函数两种声明方式。

  • 普通匿名函数:使用 delegate 关键字创建委托实例来声明。 = delegate (string str){ return str;};
  • Lambda表达式: = (参数列表 input-parameters) => { 函数体 expression; }; 括号内多个输入参数使用逗号加以分隔
class Test
{
    
    
    delegate void TestDelegate(string s);
    static void M(string s)
    {
    
    
        Console.WriteLine(s);
    }
 
    static void Main(string[] args)
    {
    
    
        //委托实例化形式1:普通的委托new方式
        TestDelegate testDelA = new TestDelegate(M);
      //委托实例化形式2:普通匿名函数方式
        TestDelegate testDelB = delegate(string s) {
    
     Console.WriteLine(s); };
        //委托实例化形式3:Lambda表达式方式
        TestDelegate testDelC = (x) => {
    
     Console.WriteLine(x); };
 
        testDelA("Hello. My name is M and I write lines.");
        testDelB("That's nothing. I'm anonymous and ");
        testDelC("I'm a famous author.");
 
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

4. C# 属性

属性语法是字段的自然延伸。 字段用来定义存储位置,而属性定义包含 getset 访问器的声明(至少有一个),这两个访问器用于检索该属性(对应的字段)的值以及对其赋值。访问属性时,其行为类似于字段。 但与字段不同的是,属性通过访问器实现;访问器用于定义访问属性或为属性赋值时执行的语句。通过属性可以提供验证、不同的可访问性、迟缓计算或方案所需的任何要求。

(1)传统属性定义

//传统属性声明
//特点:存取时可做逻辑操作,但代码量多比较繁琐
class Student
{
    
    
	//字段
    private int _age; 
    private string _name;
    private Gender _gender;

	//属性访问器
    public int Age {
    
     get {
    
     return _age; } set {
    
     _age = value; } }
    public string Name {
    
     get {
    
     return _name; } set {
    
     _name = value; } }
    public Gender Gender {
    
     get {
    
     return _gender; } set {
    
     _gender = value; } }
} 

(2)自动属性定义

自动属性有完全没有逻辑的get访问器和set访问器。在访问器后不写函数主体,而是直接以分号结尾。编译器会自动生成支持该属性的字段的存储位置,这个字段被称为匿名字段,仅供该属性访问。匿名字段没有名字,所以你不能在代码中亲自使用它。只能通过配套的自动属性间接使用。

//自动属性声明:编译器会自动把这段代码翻译成上面的完整版本
//特点:减少代码量简化开发,但属性定义时不能做其他逻辑判断只能简单存取
class Student
{
    
    
    public int Age {
    
     get; set; }
    public string Name {
    
     get; set; }
    public Gender Gender {
    
     get; set; }
}

(3) 可访问性控制

  • 访问权限

可以在访问器前使用访问修饰符,但在单个访问器上放置的任何访问修饰符都必须比属性定义上的访问修饰符提供更严格的限制。

public class Person
{
    
    
	//现在,可以从任意代码访问 FirstName 属性,但只能从 Person 类中的其他代码对其赋值。
    public string FirstName {
    
     get; private set; }

    // Omitted for brevity.
}
  • 只读/只写

还可以直接限制对属性的修改方式,即访问器的声明。

		//声明只读属性
		public ICommand CheckCommand
        {
    
    
            get
            {
    
    
                return new RelayCommand(_ =>
                {
    
    
                   //do something...
                });
            }
        }

(4)抽象属性

抽象类可拥有抽象属性,这些属性应在派生类中被实现。下面的程序说明了这点:

using System;
namespace runoob
{
    
    
   public abstract class Person
   {
    
    
      public abstract string Name
      {
    
    
         get;
         set;
      }
      public abstract int Age
      {
    
    
         get;
         set;
      }
   }
   class Student : Person
   {
    
    

      private string code = "N.A";
      private string name = "N.A";
      private int age = 0;

      // 声明类型为 string 的 Code 属性
      public string Code
      {
    
    
         get
         {
    
    
            return code;
         }
         set
         {
    
    
            code = value;
         }
      }
   
      // 声明类型为 string 的 Name 属性
      public override string Name
      {
    
    
         get
         {
    
    
            return name;
         }
         set
         {
    
    
            name = value;
         }
      }

      // 声明类型为 int 的 Age 属性
      public override int Age
      {
    
    
         get
         {
    
    
            return age;
         }
         set
         {
    
    
            age = value;
         }
      }
      public override string ToString()
      {
    
    
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
   }
   class ExampleDemo
   {
    
    
      public static void Main()
      {
    
    
         // 创建一个新的 Student 对象
         Student s = new Student();
           
         // 设置 student 的 code、name 和 age
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info:- {0}", s);
         // 增加年龄
         s.Age += 1;
         Console.WriteLine("Student Info:- {0}", s);
         Console.ReadKey();
       }
   }
}

5. C# 事件

事件建立在委托之上,与委托不同的是:委托是一个行为/动作,而事件是一个从动作发生->行为处理的过程,是个模型。事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些出现,如系统生成的通知。应用程序需要在事件发生时响应事件。
举个例子:“发生→响应”中的五个部分:闹钟响铃了小明起床。其中4个部分很容易看出:闹钟(事件源),响铃(事件),小明(订阅者),起床(事件处理行为)。其中还有一个隐含的部分为:订阅。 也就是小明订阅了他手机的铃声,手机响铃时,小明才会发生反应。如果是别人的手机,小明没有订阅,就算响铃,小明也不会有反应。

5.1 事件模型

事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

  • 发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

  • 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

5.2 事件声明

在类的内部声明事件,首先必须声明该事件的委托类型。例如:

public delegate void BoilerLogHandler(string status);

然后,声明事件本身,使用 event 关键字包装委托:

// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

5.3 事件的使用

        //事件发布者
        public class PublishEvent
        {
    
    	//拥有事件及其委托
            public delegate string Display(string str);
            public event Display DisplayEvent;

            //事件触发器
            public void Shows(string str)
            {
    
    
                if (DisplayEvent != null)
                {
    
    
                    DisplayEvent(str);
                }
            }

        }

        //事件订阅者
        public class Listen1
        {
    
    
        	//订阅事件的处理/响应方法
            public string MakeAlert(string str)
            {
    
    
                Console.WriteLine(str + "Listen1");
                return str + "Listen1";
            }
        }
		class Program
        {
    
    
            static void Main()
            {
    
    
            	//1.声明事件发布者
                PublishEvent pe = new PublishEvent();
                //2.声明订阅者
                Listen1 l1 =  new Listen1();
                Listen2 l2 = new Listen2();

                //变量l1和l2订阅了事件(当侦听到事件发生时会调用各自的方法响应事件)
                pe.DisplayEvent += l1.MakeAlert;
                pe.DisplayEvent += l2.ShowMsg;

                //触发事件->通知订阅->响应事件
                pe.Shows("事件");

                Console.ReadKey();

            }
        }

5.4 注意事项

  • 事件的本质:事件的本质是委托字段的一个包装器,是特殊的委托。这个包装是对委托起到限制作用,防止对象内部的委托实例被外部乱用,封装的一个重要的功能就是隐藏。事件对外界隐藏了委托实例的大部分功能。
    • 限制一:委托的调用没有限制,可以在任意的地方进行调用,而事件修饰过的委托只能在定义自己的类内部进行调用。
    • 限制二:委托可以使用“=”、“+=”、“-=”三种符号来表示赋值,添加和移除一个函数,而事件修饰过的委托在自己的类中也可以使用这三个符号(注意子类也属于外部的类),而在外部的类中时则只能使用“+=”和“-=”来添加移除函数,不能使用“=”进行直接赋值
  • 命名规范:用于声明事件的委托,一般命名为事件名+EventHandler。事件名+EventHandler的委托参数一般有两个(由win32API 演化而来,历史悠久)
    • 第一个参数:sender是Object类型,表示事件源\事件发送者 。
    • 第二个参数:e是EventArgs类型 。EventArgs类表示包含事件数据的类的基类,并提供用于不包含事件数据的事件的值。

6. 快捷方式

  • prop+tab : 自动创建 属性
  • propdp+tab : 自动创建 依赖属性及其包装器
  • propfull+tab : 自动创建 属性及其字段
  • propa+tab : 自动创建 附加属性

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/126336296