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 }
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 }
委托 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也许能做,但是要写很多代码。
相关内容: 点这里
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· Open-Sora 2.0 重磅开源!