C#基础

基本语法

  注意:

    区分大小写

    ;语句结尾

    与Java不同,文件名可以和类名不同

  面向对象编程

  关键字

    using  引用命名空间

    class  声明一个类

    注释方式

      单行://

      多行:/*    */

      文档:///

    变量

类型 关键字
整数类型 byte、short、int、long
浮点型 float、double
十进制类型 decimal
布尔类型 bool
字符类型 string、char
空类型 null

        decimal精度较高,但依旧会有精度损耗,使用M后缀

        

    表达式,运算符

    作用域

    

  string转换

    *.Parse(string)

    如 Int.Parse("5") 输出为 5

    Convert.TpInt32(double value)

    如果value为两个整数中间的数,则返回偶数,例如4.5输出4,5.5输出6.其他情况为四舍五入。

    Convert.ToInt32()能处理空值,返回为0,Int.Parse()空会产生异常。

  

  函数

    单一职责。

    大驼峰命名,即开头字母也大写

    参数、返回值。

    参数修饰符:

      1、无修饰符:按值传递、得到的是副本

      2、out:引用传递,可以获得多个返回值(C#7.0之后一般用元组)

      3、ref:调用者赋初值,方法里可选赋值

      4、params

      out与ref区别:

        out必须在方法内修改,ref可修改也可以不修改;

        out在传入参数时,参数是局部变量的话,可以不赋值,因为out一定会对其进行赋值;

        ref修饰的参数,必须有初始值才能调用。

 

  预编译指令

    #define、#undef

    #if、#elif、#else、#endif

    #warning、#error

    #region、#endregion

    #line

    #pragma

 对象和类型

  属性:

    public int Age {get;set;}

    public int Age {get;private set;}

 

  Object类

    System.Object 方法

      ToString()

      GetHashTable()

      Equals()

      Finalize()

      GetType()

      MemberwiseClone()

  方法隐藏

    隐藏(方法):基类方法不做申明(默认为非虚方法),在派生类中使用new声明此方法的隐藏。隐藏时,访问父类则调用父类的方法,访问子类则调用子类的方法。

    不使用new关键字会生成警告

 

继承

  抽象类

    abstract  (  :  )

    类同Java

  密封类和密封方法

    sealed 关键字。表示不能继承、重写。

   构造方法

    //无参

    子类构造方法进行构造时,会先一直向上找,直到找到顶级的父类后,开始执行构造方法,然后一直往下。所以执行顺序是父类的构造方法先执行。

    可以设置某一个父类的构造方法为私有的,这样就打乱了执行顺序,会报错。

    //有参

复制代码
      
 1     abstract class GenericCustomer
 2     {
 3         private string name;
 4         public GenericCustomer(string name)
 5         {
 6             this.name = name;
 7             Console.WriteLine(this.name);
 8         }
 9     }
10 
11     class Nevermore60Customer : GenericCustomer
12     {
13         public Nevermore60Customer(string name) : base (name)
14         {
15 
16         }
17 
18         private uint highCostMinutesUsed;
19     }
情况一
复制代码
复制代码
      
 1     abstract class GenericCustomer
 2     {
 3         private string name;
 4         public GenericCustomer(string name)
 5         {
 6             this.name = name;
 7             Console.WriteLine(this.name);
 8         }
 9     }
10 
11     class Nevermore60Customer : GenericCustomer
12     {
13 
14 
15         public Nevermore60Customer(string name, string referrerName) : base(name)
16         {
17             this.referrerName = referrerName;
18         }
19         public Nevermore60Customer(string name) : this(name, "<none>")
20         {
21 
22         }
23 
24         private uint highCostMinutesUsed;
25         private string referrerName;
26     }
情况二
复制代码

       Nevermore60Customer nevermore60Customer = new Nevermore60Customer("tsetName");

      情况一没有其他的特殊情况直接传

      情况二有多个要单独写一个出来

  修饰符

    可见:

      public、protected、internal、private、protected internal

    其他:

      new、static、virtual、abstract、override、scaled、extern

   接口

    interface (  :  )

    类同Java

    作用:

      1、拓展一个已有类的行为

      2、规范不同类型的行为

    特点:

      1、接口是抽象的,接口是一组行为的抽象。只表达”能做什么“,不表达“怎么做”。

      2、接口是规范,定义一组对外的行为规范。实现类必须实现接口的所有成员。

     语法特点:

      1、接口不能包含字段,可以包含:行为【方法、属性、索引器、事件】

      2、接口中所有的成员不能加任何访问修饰符,全部默认公有

      3、接口中所有成员不能有实现,全部默认抽象。(试了加方法,编译器不报错,但方法没办法使用。可以加静态方法,也能使用,此处存疑)

      4、实现类实现接口用“ : ”,与继承相同

      5、实现类可以实现多个接口,所有方法都要实现。(类继承只能有一个)

      6、接口中的成员在实现类中以public的方式实现(除显示实现)

      7、接口的引用可以指向实现类的对象,接口 obj=new 实现类()

    接口的使用:

      语法:

      1、类继承接口 ClassA : interfaceB, interfaceC

      2、接口继承接口 interface : interfaceB, interfaceC

      3、struct结构体可以继承接口,但不能继承类

      类实现接口的方式

      1、隐式实现(常规方法)

      public 数据类型 接口方法(){方法体}

      2、显示实现(非常规方法,很少用,了解一下就好)

      数据类型 接口名.接口方法(){方法体}

      当成私有的方法使用,外部无法访问,除非 接口 A=new 继承类,此时A可以在外部访问。

      显示实现的作用:

      1、解决接口中的成员对实现类不适用的问题

      即:接口中有若干方法该类不需要实现,使用显示实现在外部写代码时将不会显示。一定程度上减少代码污染

      2、解决多接口实现时的二义性问题(用的更少)

      即:接口A中有说话的方法,接口B也有说话的方法,当一个类同时继承的时候,系统不知道想实现哪个接口,此时可以使用显示实现,不用也不会报错。

      (接口此部分,参考此博客)

     

泛型

     装箱和拆箱

      值类型转换为应用类型为装箱

      引用类型转换为值类型为拆箱

    装箱和拆箱的操作都很简单,但性能损失较大,遍历多项时尤其如此。

    约束了数组可以添加的类型

    常见泛型类型

      1、泛型类

        class MyGenericClass<T>  {  //......  }

      2、泛型接口

        interface GenericInterface<T>  {  void GenericMethod(T t);  }

      3、泛型方法

        public void MygenericMethod<T>()  {  //.......  }

      4、泛型数组

        public T[ ]  GenericArray;

      5、泛型委托

        public delegate Toutput GenericDelagete<TInput, TOutput>(TInput input);

      6、泛型结构

        struct MyGenericStruct<T>  {    }

     使用通用类泛型的好处在于,获取不同对象集合不需要写多个方法,只需要将需要获取的类型设置即可。

    泛型类型参数约束

      1、where T : struct

        类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。

      2、where T : class

        类型参数必须是引用类型;这一点也适用于任何类、接口、委托或者数组类型。

      3、where T : new()

        类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new()约束必须最后指定

      4、where T : <基类名>

        类型参数必须是指定的基类或者派生自指定的基类。

      5、where T : <接口名称>

        类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

      6、where T1 : T2

        为 T1 提供的类型参数必须是为 T2 提供的参数或派生自为 T2 提供的参数。

    泛型委托

      泛型委托可以自己定义自己的类型参数,声明的时候还是和泛型类、泛型方法一样加个<T>,泛型委托使用较少,且要慎用!

    泛型部分参考文献:点这里

     补充

      default关键字,在泛型中,根据泛型类型是引用类型还是值类型,泛型default用于将泛型类型初始化为null或0;

复制代码
    
 1 using System;
 2 
 3 using System.Collections.Generic;
 4 
 5 namespace Wrox.ProCSharp.Generics
 6 {
 7     class program
 8     {
 9         public static void Main(string[] arg)
10         {
11             var dm = new DocumentManager<Document>();
12             dm.AddDocument(new Document("Title A", "Sample A"));
13             dm.AddDocument(new Document("Title B", "Sample B"));
14 
15             dm.DisplayAllDocuments();
16 
17             if (dm.IsDocumentAvailable)
18             {
19                 Document d = dm.GetDocument();
20                 Console.WriteLine(d.Content);
21             }
22         }
23     }
24     public class DocumentManager<TDocument>
25         where TDocument:IDocument
26     {
27         private readonly Queue<TDocument> documentQueue = new Queue<TDocument>();
28         public void AddDocument(TDocument doc)
29         {
30             lock (this)
31             {
32                 documentQueue.Enqueue(doc);
33             }
34         }
35         public bool IsDocumentAvailable
36         {
37             get { return documentQueue.Count > 0; }
38         }
39         public TDocument GetDocument()
40         {
41             TDocument doc = default(TDocument);
42             lock (this){
43                 doc = documentQueue.Dequeue();
44             }
45             return doc;
46         }
47         public void DisplayAllDocuments()
48         {
49             foreach(TDocument doc in documentQueue)
50             {
51                 Console.WriteLine(doc.Title);
52             }
53         }
54         
55     }
56     public interface IDocument
57     {
58         string Title { get; set; }
59         string Content { get; set; }
60     }
61 
62     public class Document : IDocument
63     {
64         public string Title { get; set; }
65         public string Content { get; set; }
66         public Document() { }
67 
68         public Document(string title,string content)
69         {
70             this.Title = title;
71             this.Content = content;
72         }
73 
74     }
75 
76 
77 }
测试代码
复制代码

 

数组

    Array

复制代码
    
  1 using System;
  2 using System.Diagnostics.CodeAnalysis;
  3 
  4 namespace ArrayStudy
  5 {
  6     class ArrayStudy
  7     {
  8         public static void Main(string[] args)
  9         {
 10             Array intArray1 = Array.CreateInstance(typeof(int), 5); //第一个是参数类型,第二个是数组大小
 11             for (int i = 0; i < 5; i++)
 12             {
 13                 intArray1.SetValue(33, i);  //第一个是值,第二个是位置
 14             }
 15             for(int i = 0; i < 5; i++)
 16             {
 17                 Console.WriteLine(intArray1.GetValue(i));
 18             }
 19             int[] intArray2 = (int[])intArray1; //将已创建的数组强制转换声明成int[]的数组
 20 
 21             int[] lengths = { 2, 3 };   //表示这个数组的大小是2*3  
 22             int[] lowerBounds = { 1, 10 };  //表示这个数组的下标是从[1,10]开始
 23             Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds);
 24 
 25             racers.SetValue(new Person
 26             {
 27                 Name = "Frisx",
 28                 Age=22
 29             },1,10) ;
 30             racers.SetValue(new Person
 31             {
 32                 Name = "Irster",
 33                 Age = 35
 34             }, 1, 11);
 35             racers.SetValue(new Person
 36             {
 37                 Name = "Waer",
 38                 Age = 28
 39             }, 1, 12);
 40             racers.SetValue(new Person
 41             {
 42                 Name = "flower",
 43                 Age = 18
 44             }, 1, 11);
 45             racers.SetValue(new Person
 46             {
 47                 Name = "aiwe",
 48                 Age = 22
 49             }, 2, 10);
 50             racers.SetValue(new Person
 51             {
 52                 Name = "diadl",
 53                 Age = 24
 54             }, 2, 11);
 55             racers.SetValue(new Person
 56             {
 57                 Name = "alisa",
 58                 Age = 42
 59             }, 2, 12);
 60             foreach(var item in racers)
 61             {
 62                 Console.WriteLine(item.ToString());
 63             }
 64 
 65             //Person[,] racers2 = (Person[,])racers;  
 66             //Person first = racers2[1, 10];  //正常赋值是可行的
 67             //Person last = racers2[2, 12];
 68             //Console.WriteLine(first.ToString() + "\n" + last.ToString());
 69 
 70             int[] intArray3 = { 1, 2 };
 71             int[] intArray3Clone = (int[])intArray3.Clone();
 72             intArray3Clone[1] = 3;
 73             Console.WriteLine(intArray3[1].ToString());
 74 
 75             Person[,] racersClone = (Person[,])racers.Clone();
 76             racersClone[1, 10].Age = 24;
 77             //不让使用racers[1,10]访问,会报索引的错。原因为声明的时候是用的new,再具体也不清楚了
 78             Console.WriteLine(racers.GetValue(1,10).ToString()+"     "+racersClone[1,10].ToString());
 79             /**
 80              * 从结果可知值克隆是直接给了个新的
 81              * 而引用类型克隆是直接克隆的引用,不克隆元素
 82              */
 83 
 84             /**
 85              * 排序问题
 86              * 正常可以直接用Sort方法:Array.Sort(ArrayName)
 87              * 
 88              * 如果是自定义类,就必须实现IComparable接口
 89              * 这里面只定义了一个ComparaTo方法
 90              * 比较对象相等吗,返回0,
 91              * 实例对象在参数对象前面,返回 小于0的值,反之返回 大于0的值
 92              * 
 93              */
 94 
 95             Employee[] employees =
 96             {
 97                 new Employee{Name="Frisx",Age=23},
 98                 new Employee{Name="Ister",Age=38},
 99                 new Employee{Name="Ister",Age=22},
100                 new Employee{Name="Alisa",Age=42}
101 
102             };
103             Array.Sort(employees);
104             foreach(var item in employees)
105             {
106                 Console.WriteLine(item.ToString());
107             }
108 
109 
110 
111 
112         }
113     }
114 
115     public class Employee : IComparable<Employee>
116     {
117         public string Name { get; set; }
118         public int Age { get; set; }
119 
120         public int CompareTo(Employee employee)
121         {
122             if (employee == null) throw new ArgumentNullException("employee");
123             int result = this.Name.CompareTo(employee.Name);
124             if (result == 0)
125             {
126                 result = this.Age.CompareTo(employee.Age);
127             }
128             return result;
129         }
130         public override string ToString()
131         {
132             return $"Name = {Name},  Age = {Age}";
133         }
134     }
135     class Person
136     {
137         public string Name { get; set; }
138         public int Age { get; set; }
139 
140         public override string ToString()
141         {
142             return $"Name = {Name}, Age = {Age}";
143         }
144     }
145 }
Array 相关代码
复制代码

    yiled

    yield关键字作用是将当前集合中的元素立刻返回

      1、返回元素用yield return;(一次一个的返回)

      2、结束返回用yield break;(终止迭代)

      3、返回类型必须为 IEnumerable、IEnumerable<T>、IEnumerator 或 IEnumerator<T>。

      4、参数前不能使用ref和out关键字

      5、匿名方法中 不能使用yield

      6、unsef中不能使用

       7.不能将 yield return 语句置于 try-catch 块中。 可将 yield return 语句置于 try-finally 语句的 try 块中。yield break 语句可以位于 try 块或 catch 块,但不能位于 finally 块

      yield处参考,点这里

     元组

     数组合并了相同类型的对象,而元组合并了不同类型的对象。

    定义:元组是包含多个字段以表示数据成员的轻量级数据结构。

    当然元组是比 class 和 struct 类型更为简单灵活的数据容器

    语法 

复制代码
    
            //1
            var tuple = (5, 10);
            //or   var tuple = (5,10);  

            //2
            (int a, int b) tuple1 = (5, 10);
            // or  (int,int) tuple1 = (5,10);

            Console.WriteLine($"{tuple.Item1},{tuple.Item2}"); //display  5,10
            Console.WriteLine($"{tuple1.a},{tuple1.b}");//display 5,10

            Console.WriteLine(tuple1.a);//5
            Console.WriteLine( tuple1.b);//10
        
元组相关代码
复制代码

    

运算符和类型强制转换

类别 运算符
算术运算符 +  -  *  /  %
逻辑运算符‘ &  |  ^  ~  &&  ||  !
字符串连接运算符 +
增量和减量运算符 ++  --
移位运算符 <<  >>
比较运算符 ==  !=  <>  <=  >=
赋值运算符 =  +=  -=  *=  /=  %=  &=  |=  ^=  <<=  >>=
成员访问运算符(用于对象和结构) .
索引运算符(用于数组和索引器) []
类型转换运算符 ()
条件运算符(三元运算符) ?:
委托连接和删除运算符 +  -
对象创建运算符 new
类型信息运算符 sizeof  is  typeof  as
溢出异常控制运算符 checked  unchecked
间接寻址运算符 []
名称空间别名限定符 ::
空合并运算符 ??

    sizeof(仅用于.NET Framework 1.0 和 1.1)

    条件运算符(  ?:)

      condition ? true_value : false_value

      其中condition是要判断的bool条件,后面是判断后返回的值

    checked和unchecked

      检查是否溢出,默认都是unchecked

    is运算符

      is 用于检查对象是否与特定的类型兼容

    as运算符

      as 用于执行引用类型的显示类型转换。如果要转换的类型与指定的类型兼容,转换成功进行;如果类型不兼容,as运算符会返回 null 值。

    sizeof运算符

      使用四则偶分运算符可以确定栈中值类型需要的长度(以字节为单位)

    typeof运算符

      typeof 运算符返回一个特定类型的 System.Type 对象。

    可空类型和运算符

    空合并运算符

      运算符放在两个操作数中间,第一个操作数必须是一个可空类型或引用类型;第二个操作数必须与第一个操作数的类型相同,或者可以隐含地转换为第一个操作数的类型,计算规则是:

      如果第一个操作数不是null,整个表达式就等于第一个操作数的值。

      如果第一个操作数是null,整个表达式就等于第二个操作数的值。

    运算符优先

    类型转换

      隐式转换:值不会发生任何改变,类型转换自动进行

      显示转换:()强制转换,不会报错,但会丢失数据

    装箱和拆箱

      装箱用于描述把一个值类型强制转换为引用类型。运行库会为堆上的对象创建一个临时的引用类型“箱子”。

      拆箱用于描述相反的过程,其中以前装箱的值类型强制转换回值类型。

    比较对象的相等性

 

       1、ReferenceEquals()

      2、虚拟的Equals()方法  一般重写

      3、静态的Equals()方法  带有两个参数

      4、比较运算符( == )

    运算符重载

      通过特定的语法,使某些运算符可以具备特殊的功能。

      关键字 oprator ,修饰符必须为 public static 

      比较运算符分为3对,(==和!=、>和<、>=和<=)。重载的话必须成对重载。

      并不是所有的运算符都能重载

 

 

    自定义类型的强制转换

      (待补充)

    

委托、Lambda 表达式和事件

  委托

    委托是一种数据类型。

    把要变化的地方封装好,到时候传进需要的方法进来,提高代码复用性

    委托是用户自定义的类,它定义了方法的类型。储存的是一些列具有相同参数和返回类型方法的地址列表,调用委托时,委托列表的所有方法都将被执行。

    委托是一个对象,它知道如何调用一个方法

    委托类型和委托实例

      委托类型定义了委托实例可以调用的那类方法,具体来说,委托类型定义了方法的返回类型和参数

      委托实例:把方法赋值给委托变量的时候就创建了委托实例

      委托的实例其实就是调用者的委托:调用者调用委托,然后委托调用目标方法。

      间接的把调用者和目标方法解耦合了。

复制代码
      
 1 using System;
 2 
 3 namespace DelegateEventLambda
 4 {
 5     //带一个参数并且无返回值的委托
 6     public delegate void Speak(string content); 
 7     class DelegateEventLambda
 8     {
 9         //SPeak类型的事件
10         public static event Speak SpeakEvent;
11         static void Main(string[] args)
12         {
13             //委托的声明
14             //委托的使用方法
15             //委托的解释:将方法以变量的形式传递,并且以方法的形式执行
16             //Speak dlg = new Speak(SayWhat);   //完整写法
17             Speak dlg = SayWhat;
18 
19             //委托链(多播委托)
20             dlg += SayEnglish;
21             dlg -= SayEnglish;
22             dlg("李华");
23 
24             //匿名函数
25             Speak dlg1 = delegate (string name)
26               {
27                   Console.WriteLine($"{name},我是匿名函数");
28               };
29             //dlg1.Invoke("李华");        //完整写法
30             dlg1("李华");
31             
32 
33             //lambda语句
34             Speak dlg2 = (name) =>
35             {
36                 Console.WriteLine( $"{name},我是lambda语句");
37             };
38             dlg2("李华");
39 
40             //注册事件
41             SpeakEvent += Program_SpeakEvent;
42             if (SpeakEvent != null)
43             {
44                 SpeakEvent("老王");   //调用事件
45             }
46 
47         }
48 
49         private static void Program_SpeakEvent(string name)
50         {
51             Console.WriteLine($"{name},我是事件!");
52         }
53 
54         public static void SayWhat(string name)
55         {
56             Console.WriteLine($"{name},讲中文");
57         }
58 
59         public static void SayEnglish(string name)
60         {
61             Console.WriteLine($"{name},讲英文");
62         }
63     }
64 
65 }
委托相关代码
复制代码

    编写插件式的方法

      方法是在运行时才赋值给委托变量的

复制代码
      
 1 using System;
 2 
 3 namespace Delegate2
 4 {
 5     public delegate int Transformer(int x);
 6     public class Util
 7     {
 8         public static void Transfrom(int[] values, Transformer t)
 9         {
10             for(int i = 0; i < values.Length; i++)
11             {
12                 values[i] = t(values[i]);
13             }
14         }
15 
16         class Test
17         {
18             static void Main()
19             {
20                 int[] values = { 1, 2, 3 };
21                 Util.Transfrom(values, Square);
22                 foreach (int i in values)
23                     Console.Write(i + " ");
24             }
25             static int Square(int x) => x * x;
26         }
27     }
28 }
插件式委托
复制代码

    多播委托(委托链)

      所有的委托实例都具有多播的能力。一个委托实例可以引用一组目标方法。

      +、+=、-、-=  委托变量可以为null

      委托是不可变的

      使用 += 或 -= 操作符时,实际上是创建了新的委托实例,并把它赋给当前的委托变量

      如果多播委托的返回类型不是void,那么调用者从最后一个被调用的方法来接收返回值。前面的方法任然被调用,但其返回值就被弃用了。

    所有的委托类型都派生于System.MulticastDelegate,而它又派生于System.Delegate。

    C#会把作用域委托的 +、-、+=、-=操作编译成使用System.Delegate 的 Combine 和 Remove 两个静态方法。(会进行一次强转来保证是当前所需要的类型)

    实例方法目标和静态方法目标

      当一个实例方法被赋值给委托对象的时候,这个委托对象不仅要保留着对方法的引用,还要保留着方法所属实例的引用。

      System.Delegate 的 Target 属性就代表着这个实例

      如果引用的是静态方法,那么Target属性就是null

    泛型委托类型

      委托类型可以包含泛型类型参数

        public delegate T Transforvmer<T>(T arg)

复制代码
    
 1 public delegate T Transformer<T>(T arg);
 2     public class Util
 3     {
 4         public static void Transfrom<T>(T[] values, Transformer<T> t)
 5         {
 6             for(int i = 0; i < values.Length; i++)
 7             {
 8                 values[i] = t(values[i]);
 9             }
10         }
11 
12         class Test
13         {
14             static void Main()
15             {
16                 int[] values = { 1, 2, 3 };
17                 Util.Transfrom(values, Square);
18                 foreach (int i in values)
19                     Console.Write(i + " ");
20             }
21             static int Square(int x) => x * x;
22         }
23     }
泛型委托
复制代码

    Func 和 Action 委托

      使用泛型委托,就可以写出这样一组委托类型,它们可调用的方法可以拥有任意的返回类型和任意(合理)数量的参数

      System 命名空间

      Func,有返回类型的委托,参数最后一个为输出,前面其他的都为输入,输入一般最多16个

      Action 没有返回类型,即为void。参数都为输入

 

复制代码
      
 1 using System;
 2 
 3 namespace Delegate2
 4 {
 5     //public delegate T Transformer<T>(T arg);
 6     public class Util
 7     {
 8         public static void Transfrom<T>(T[] values, Func<T,T> t)
 9         {
10             for(int i = 0; i < values.Length; i++)
11             {
12                 values[i] = t(values[i]);
13             }
14         }
15 
16         class Test
17         {
18             static void Main()
19             {
20                 int[] values = { 1, 2, 3 };
21                 Util.Transfrom(values, Square);
22                 foreach (int i in values)
23                     Console.Write(i + " ");
24             }
25             static int Square(int x) => x * x;
26         }
27     }
28 }
Func委托
复制代码

    委托 VS 接口

      委托可以解决的问题,接口都可以解决

      什么情况下更适合使用委托而不是接口?

        接口只能定义一个方法

        需要多播能力

        订阅者需要多次实现接口

    委托的兼容性 - 委托类型

      委托类型之间互不相容,即使数字签名一样

      如果委托实例拥有相同的方法目标,那么委托实例就认为是相等的

     委托的兼容性 - 参数

      当你调用一个方法时,你提供的参数(argument)可以比方法的参数(parameter)定义更具体。

      委托可以接受比它的方法目标更具体的参数类型,这个叫 ContraVariance

      和泛型类型参数一样,委托的 variance 仅支持引用转换

    委托的兼容性 - 返回类型

      调用方法时,你可以得到一个比请求的类型更具体的类型的返回参数 

      委托的目标方法可以返回比委托描述里更具体的类型的返回结果,Covariance

    泛型委托类型参数的variance

      Covariance,out

      Contravariance,in

  lambda表达式

    1、无参数无返回值

      MyDelegate md = () => { Console.WriteLine("无参无返回值"); };

    2、有参无返回值

      MyDelegate md = m => { Console.WriteLine(m); };  //不需要传数据类型因为委托已经限定了数据类型

    3、有参有返回值

      MyDelegate md = (x, y, z) => { return x+y+z; };

 

  事件 Event

    什么叫事件?

      时间就是委托的安全版本

      第一点,在定义事件类的外部,是不能使用 = 来操作,只能用 += 。

      第二点,在定义事件类的外部不能调用 事件

      事件就是在委托前面加一个 event 关键字

复制代码
      
  1 using System;
  2 using System.Threading;
  3 
  4 namespace Event1{
  5     
  6     class Event1
  7     {
  8         static void Main(string[] args)
  9         {
 10             #region 使用委托实现的音乐播放器
 11             //MusicPlayer mp3 = new MusicPlayer();
 12 
 13             //mp3.AfterStartedPlay = () =>
 14             //{
 15             //    Console.WriteLine("加载歌词!!!");
 16             //    Console.WriteLine("加载动感背景!!!");
 17             //};
 18 
 19             //mp3.BeforeEndMusic = () =>
 20             //{
 21             //    Console.WriteLine("删除歌词!!!");
 22             //    Console.WriteLine("关闭动感背景!!!");
 23             //};
 24 
 25             ////委托可以用=直接赋值,可以将以前"注册"的方法都覆盖掉
 26             //mp3.AfterStartedPlay = null;
 27             //mp3.BeforeEndMusic = null;
 28 
 29             //mp3.StartMusic();
 30             //mp3.EndMusic();
 31 
 32             ////因为是用委托来实现的,所以在外部可以随意调用
 33             ////此时不能将委托变成private的,如果改成私有的,则也无法为委托变量赋值了
 34             ////mp3.AfterStartedPlay();
 35             ////mp3.BeforeEndMusic();
 36             #endregion
 37 
 38             #region 用event事件来实现音乐播放器
 39             MusicPlayer mp3 = new MusicPlayer();
 40             mp3.AfterStartedPlay += new Action(mp3AfterStartedPlay);
 41             mp3.BeforeEndMusic += new Action(mp3BeforeMusicStop);
 42 
 43             mp3.StartMusic();
 44             mp3.EndMusic();
 45 
 46             //事件不能在外部直接调用
 47             //事件只能在定义事件的类的内部来触发
 48             //mp3.AfterStartedPlay();
 49             //mp3.BeforeEndMusic();
 50 
 51             #endregion
 52         }
 53         static void mp3BeforeMusicStop()
 54         {
 55             Console.WriteLine("删除歌词。。。。。。");
 56         }
 57         static void mp3AfterStartedPlay()
 58         {
 59             Console.WriteLine("加载歌词。。。。。。");
 60         }
 61     }
 62 
 63     public class MusicPlayer
 64     {
 65         //做几件事
 66         //1、音乐开始播放后触发某个事件
 67         public event Action AfterStartedPlay;
 68 
 69         //2、音乐停止播放之前触发某个事件
 70         public event Action BeforeEndMusic;
 71 
 72 
 73         private void PlayMusic()
 74         {
 75             Console.WriteLine("开始播放音乐。。。。。。");
 76 
 77         }
 78 
 79         /// <summary>
 80         /// 按下【播放】按钮实现播放音乐
 81         /// </summary>
 82         public void StartMusic()
 83         {
 84             PlayMusic();
 85             AfterStartedPlay?.Invoke();
 86             Thread.Sleep(3000);
 87         }
 88         /// <summary>
 89         /// 音乐播放完毕!
 90         /// </summary>
 91         public void EndMusic()
 92         {
 93             BeforeEndMusic?.Invoke();
 94             Console.WriteLine("音乐播放完毕!!");
 95         }
 96 
 97     }
 98 
 99     /// <summary>
100     /// 音乐播放器类
101     /// </summary>
102     //public class MusicPlayer
103     //{
104     //    //做几件事
105     //    //1、音乐开始播放后触发某个事件
106     //    public Action AfterStartedPlay;
107 
108     //    //2、音乐停止播放之前触发某个事件
109     //    public Action BeforeEndMusic;
110 
111 
112 
113 
114     //    private void PlayMusic()
115     //    {
116     //        Console.WriteLine("开始播放音乐。。。。。。");
117 
118     //    }
119 
120     //    /// <summary>
121     //    /// 按下【播放】按钮实现播放音乐
122     //    /// </summary>
123     //    public void StartMusic()
124     //    {
125     //        PlayMusic();
126     //        AfterStartedPlay?.Invoke();
127     //        Thread.Sleep(3000);
128     //    }
129     //    /// <summary>
130     //    /// 音乐播放完毕!
131     //    /// </summary>
132     //    public void EndMusic()
133     //    {
134     //        BeforeEndMusic?.Invoke();
135     //        Console.WriteLine("音乐播放完毕!!");
136     //    }
137 
138     //}
139 }
音乐播放器示例
复制代码

 

    广播和订阅

       广播和订阅使用委托的时候,通常会出现两个角色,一个广播者,一个订阅者

      广播者这个类型包含一个委托字段,广播者通过调用委托来决定什么时候进行广播

      订阅者是方法目标的接收者,订阅者可以决定何时开始或结束监听,方式是通过在委托调用 += 和 -= 。

      一个订阅者不知道和不干扰其他的订阅者

    Event 事件

      时间就是将上述模式正式化的一个语言特性

      时间是一种结构,为了实现广播 / 订阅者模型,它只暴露了所需的委托特性的部分子集(区别)

      时间的主要目的就是防止订阅者之间相互干扰

     声明事件

      最简单的声明事件就是委托前面加上event关键字

    事件实例

复制代码
    
 1 using System;
 2 using System.Timers;
 3 
 4 namespace EventStudy
 5 {
 6     //主要作用是 指定了事件处理方法必须拥有的返回类型和参数
 7     public delegate void MessageHandler(string messageText, int num);
 8 
 9     public class Connection
10     {
11         //声明事件后,就可以引发它(使用和委托出发的方法)
12         //理解:它就是一个其返回类型和参数是由委托指定的方法一样,通过方法的调用来引用事件
13         public event MessageHandler MessageArrived;
14         private Timer pollTimer;
15 
16         public Connection()
17         {
18             pollTimer = new Timer(1000);
19             pollTimer.Elapsed += new ElapsedEventHandler(checkForMessage);
20         }
21 
22         public void Connect()
23         {
24             pollTimer.Start();
25         }
26         public void DisConnect()
27         {
28             pollTimer.Stop();
29         }
30         private static Random random = new Random();
31         private void checkForMessage(object sender,ElapsedEventArgs e)
32         {
33             Console.WriteLine("checking for new message");
34             //检查事件是否有订阅者,以此决定是否触发事件
35             if ((random.Next(9) == 0) && (MessageArrived != null))
36             {
37                 //触发事件,出发后,将参数传递给他绑定的事件处理器
38                 MessageArrived("Hello", random.Next(9));
39             }
40         }
41     }
42 
43     //订阅事件的类
44     public class Display
45     {
46         public void DisplayMessage(string message,int num)
47         {
48             Console.WriteLine($"Message arrived:{message}:{num}");
49         }
50     }
51 
52     class Program
53     {
54         static void Main(string[] args)
55         {
56             Connection myConnection = new Connection();
57             Display myDisplay = new Display();
58             myConnection.MessageArrived += new MessageHandler(myDisplay.DisplayMessage);
59             myConnection.Connect();
60             Console.ReadKey();
61         }
62     }
63 }
简单定时器触发事件
复制代码

    (理解不深,待补充)

字符串和正则表达式

    System.String 类

      string 类

        不可修改,每次 += 都是分配一个新的内存 

      StringBuilder类

        可修改,字符串有的基本都有。而且文本替换性能高很多

      格式字符串

   

  正则表达式

    概述:

      可以用几行代码就做到字符串的一些需求,用StringBuilder也许能做,但是要写很多代码。

     相关内容: 点这里

 

 

 

 

 

  

 

posted @   xunzf  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示