时过境迁,今年java求职是真的难,boss直聘往年每天99+,今年仿佛落寂深林一般。此诚危急存亡之秋也。好在最近内推进了一家,不过做的.net,这不重新捡起大学老本么。为了快速上手公司项目,赶紧温习些重点。
目录
辅助学习文档:C# 封装 | 菜鸟教程 (runoob.com)
结构体struct
数据类型
访问修饰符
特性(Attribute)
规定特性:
预定义特性:
1. AttributeUsageAttribute:该特性可以用于声明其他特性的使用约束。通过指定 AttributeTargets 枚举作为参数,可以限制特性应用的类型。例如:
2. ConditionalAttribute:此特性可以用于指示方法或属性在编译时预处理期间是否应该被忽略。例如:
3. ObsoleteAttribute:该特性可以用于声明过时的方法或类型。其第一个参数是对比较新的 API 的警告消息。
4. SerializableAttribute:当需要将对象序列化为字节数组以便存储或传输时,可以应用此特性来控制序列化行为。
反射(reflection)
System.Type类
索引器(indexer)
委托(delegate)
委托的声明和使用
委托链:C#中的委托也支持委托链,即将多个委托合并在一起以便同时调用。例如将两个委托合并可以使用+运算符,而将一个委托从另一个委托中删除则使用-运算符。
泛型委托
委托中的Lambda表达式
使用委託代替Action類型
事件(event)
事件委托:
Observer设计模式
.NET 框架中的委托与事件
委托和方法的异步调用
多线程
使用Thread类
使用Task类
使用ThreadPool类
集合
List
Dictionary
Queue
Stack
其他补充
定义及作用
- 在 C# 中,结构体(struct)是一种用户自定义的值类型,它可以包含不同数据类型的成员变量和方法。与类不同,结构体是轻量级的,并且是按值传递的。
- 下面是一个示例结构体的定义:
public struct Point { public int X; // 结构体中的公共字段 public int Y; public Point(int x, int y) // 结构体中的构造函数 { X = x; Y = y; } public void Display() // 结构体中的方法 { Console.WriteLine("X = {0}, Y = {1}", X, Y); } } // 使用结构体时,可以像使用其他类型的变量一样来声明和初始化它: Point point1 = new Point(10, 20); // 也可以直接对结构体进行赋值: Point point2; point2.X = 30; point2.Y = 40;
需要注意的是,当定义一个结构体时,必须为每个公共字段分配默认值,否则编译器会报错。默认情况下,所有的字段都被初始化为零值。
结构体的作用范围被限定在定义它的命名空间内,在方法或属性内部创建的结构体对象将在方法执行结束后就会立即销毁。因此,它们的生命周期较短,适合用于小型数据类型或者要频繁创建和销毁的对象。
与类的区别
- 1. 存储方式: 结构体是值类型,而类是引用类型。值类型存储在栈中,而引用类型存储在堆中。
- 2. 性能: 在声明变量时,对于简单的数据类型和小的对象,使用结构体可能比使用类更快,因为它们不需要从堆中分配内存和回收内存。
- 3. 继承: 类支持继承,而结构体不支持继承。
- 4. 初始化: 结构体可以有无参构造函数和参数化构造函数,但没有析构函数。类可以有无参、参数化构造函数和析构函数。
- 5. 赋值: 对结构体的赋值操作会创建该结构体的一个副本,并将其存储在另一块内存中;而对类进行赋值操作时,只是将一个指向原始对象的新引用存储在另一个变量中,两个变量将引用同一个对象。
总的来说,结构体和类之间的区别在于它们的实现方式不同。如果你要处理较小的数据类型或者要频繁地创建和销毁对象,使用结构体比较合适;如果你需要支持继承、动态多态性等高级功能,那么使用类就更加适合。
- 以下是 C# 中常用的数据类型及其取值范围和内存占用大小等信息的表格:
- 特殊类型:
- 动态(Dynamic)类型
- 您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
- 声明动态类型的语法:dynamic
= value;
- 指针类型(Pointer types)
- 指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
- 声明指针类型的语法:type* identifier;
- 注意:以上表格中数据类型所占内存大小是在 32 位机器上运行时的情况,64 位机器上可能会略有不同。另外,C# 中的许多数据类型也有其它的属性和方法,因此可以扩展它们的功能来满足特定的需求。
- public:公共的,对所有程序集可见。
- private:私有的,只对当前类可见,不能被其它类引用。
- protected:受保护的,对当前类和派生类可见,其他类不能访问该成员。
- internal:内部的,对同一程序集中的类可见,对其他程序集不可见。
- protected internal:受保护的内部的,对同一程序集中的类以及派生类可见,对其他程序集不可见。
- 定义:用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
- 特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。
- 作用:
- - 帮助编写器检查代码错误
- - 控制编译器行为
- - 支持自定义属性的序列化和反序列化
- - 暴露调试、测试和执行信息
规定特性:
- [attribute(positional_parameters, name_parameter = value, ...)]element
- 特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。
预定义特性:
- C#语言中的特性(Attribute)是在代码中可用于添加注释和元数据的一种语言结构。特性提供了一种简单而强大的方式来描述类型、成员和程序集等元素,以便能够在运行时进行获取或分析。
1. AttributeUsageAttribute:该特性可以用于声明其他特性的使用约束。通过指定 AttributeTargets 枚举作为参数,可以限制特性应用的类型。例如:
[AttributeUsage(AttributeTargets.Class)] public class MyCustomAttribute : Attribute { // ... }
- 语法:[AttributeUsage( validon, AllowMultiple=allowmultiple, Inherited=inherited)]
- 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
- 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
- 例如:[AttributeUsage(AttributeTargets.Class |AttributeTargets.Constructor |AttributeTargets.Field |AttributeTargets.Method |AttributeTargets.Property, AllowMultiple = true)]
2. ConditionalAttribute:此特性可以用于指示方法或属性在编译时预处理期间是否应该被忽略。例如:
[Conditional("DEBUG")] public void Log(string message) { // ... }
这表示 Log 方法仅在调试模式下编译并包含在二进制文件中。
3. ObsoleteAttribute:该特性可以用于声明过时的方法或类型。其第一个参数是对比较新的 API 的警告消息。
- 例如:
[Obsolete("Use the NewMethod instead.")] public void OldMethod() { // ... }
这将使编译器在代码中使用此方法时生成一个警告。
4. SerializableAttribute:当需要将对象序列化为字节数组以便存储或传输时,可以应用此特性来控制序列化行为。
- 例如:
[Serializable] public class MyCustomClass { public int Id { get; set; } public string Name { get; set; } }
- 定义:
- 反射指程序可以访问、检测和修改它本身状态或行为的一种能力。
- 程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。
- 您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性。
- 用途:
- 它允许在运行时查看特性(attribute)信息。
- 它允许审查集合中的各种类型,以及实例化这些类型。
- 它允许延迟绑定的方法和属性(property)。
- 它允许在运行时创建新类型,然后使用这些类型执行一些任务。
- 查看元数据:
- System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的特性(attribute)
- System.Reflection.MemberInfo info = typeof(MyClass);
System.Type类
- 在C#中,Type类表示一个类型(也称为一个类或结构)的元数据。使用Type类,您可以获取有关类型的各种信息,例如类型的名称、命名空间、基类型、实现的接口、字段、属性、方法等。
using System; class MyClass { public int MyField; public void MyMethod() { } } class Program { static void Main(string[] args) { Type type = typeof(MyClass); Console.WriteLine("类型名:" + type.Name); Console.WriteLine("所属命名空间:" + type.Namespace); Console.WriteLine("基类型:" + type.BaseType.Name); Console.WriteLine("字段:"); foreach (var field in type.GetFields()) { Console.WriteLine("- " + field.Name + "(" + field.FieldType + ")"); } Console.WriteLine("方法:"); foreach (var method in type.GetMethods()) { Console.WriteLine("- " + method.Name + "(" + method.ReturnType + ")"); } } }
- Type类的GetCustomAttributes()方法用于获取特定类型及其成员上应用的自定义属性集合。
- Type类的GetCustomAttributes()方法的第一个参数是要获取的自定义属性的类型,第二个参数指示是否搜索继承链并获取基类上的自定义属性。返回值是一个object数组,其中包含应用到类型或成员上的所有特定的自定义属性实例。
using System; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class MyAttribute : Attribute { public string MyProperty { get; set; } public MyAttribute(string propertyValue) { MyProperty = propertyValue; } } [My("TestClass")] class MyClass { [My("TestMethod")] public void MyMethod() { } } class Program { static void Main(string[] args) { Type type = typeof(MyClass); // 获取类型的自定义属性 object[] attributes = type.GetCustomAttributes(typeof(MyAttribute), true); foreach (MyAttribute attribute in attributes) { Console.WriteLine("类的自定义属性:{0}", attribute.MyProperty); } // 获取方法的自定义属性 var method = type.GetMethod("MyMethod"); attributes = method.GetCustomAttributes(typeof(MyAttribute), true); foreach (MyAttribute attribute in attributes) { Console.WriteLine("方法的自定义属性:{0}", attribute.MyProperty); } } }
- 在该示例中,首先定义了一个MyAttribute类作为自定义属性,并将其应用于MyClass类和其中的MyMethod方法。然后,我们使用Type类和MethodInfo类的GetCustomAttributes()方法获取MyClass类及其MyMethod方法上应用的MyAttribute自定义属性并输出其值。
- 索引器(Indexer) 允许一个对象可以像数组一样使用下标的方式来访问。
- 一维索引器的语法:
element-type this[int index] { // get 访问器 get { // 返回 index 指定的值 } // set 访问器 set { // 设置 index 指定的值 } }
- 示例
class IndexerTest { private string[] namelist = new string[size]; public static int size = 10; public IndexerTest() { for(int i = 0; i < size; i++) { namelist[i] = "N.S."; } } public string this[int index] { get { string temp; if (index >= 0 && index <= size - 1) { temp = namelist[index]; } else { temp = ""; } return (temp); } set { if (index >= 0 && index <= size - 1) { namelist[index] = value; } } } static void Main(string[] args) { IndexerTest names = new IndexerTest(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; for (int i = 0; i < IndexerTest.size; i++) { Console.WriteLine(names[i]); } Console.ReadLine(); } }
- 优点:
- 1.方便地访问集合元素:通过索引器,您可以方便地像访问数组一样访问集合中的元素。这样就可以编写更灵活、更易用的代码,同时将代码与处理数据相关的其他细节分离开来。
- 2.提供更多的灵活性:索引器可以有多个参数,这意味着您可以将其用于操作多维集合和其他更加复杂的数据结构。
- 3.更好的可读性:使用索引器可以使您的代码更具可读性。它可以让您直接处理数据集合,而无需编写常规的附加函数或API调用来进行相同的操作。
- 4.更好的性能:在某些情况下,使用索引器比使用属性更快。这是因为多次调用setter和getter方法可能导致额外的开销和性能损失,而使用索引器可以更快地读取或设置对象的变量。
- 总之,索引器通常在处理集合和其他数据结构时更容易并更加灵活。当您需要可读性强的代码、更好的维护性和更好的性能时,使用索引器可能是一个不错的选择。
- 索引器的重载
- 索引器(Indexer)可被重载。索引器声明的时候也可带有多个参数,且每个参数可以是不同的类型。没有必要让索引器必须是整型的。C# 允许索引器可以是其他类型,例如,字符串类型。
- 例如:
class IndexerTest { private string[] namelist = new string[size]; public static int size = 10; public IndexerTest() { for(int i = 0; i < size; i++) { namelist[i] = "N.S."; } } public string this[int index] { get { string temp; if (index >= 0 && index <= size - 1) { temp = namelist[index]; } else { temp = ""; } return (temp); } set { if (index >= 0 && index <= size - 1) { namelist[index] = value; } } } public int this[string name] { get { int index = 0; while (index < size) { if (namelist[index] == name) { return index; } index++; } return index; } } static void Main(string[] args) { IndexerTest names = new IndexerTest(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif"; names[4] = "Davinder"; names[5] = "Sunil"; names[6] = "Rubic"; for (int i = 0; i < IndexerTest.size; i++) { Console.WriteLine(names[i]); } Console.WriteLine(names["Nuha"]);//返回2 Console.ReadLine(); } }
- 定义:
- C#中的委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用。委托通常用于实现回调机制、事件和可插入式(pluggable)体系结构。C#中的委托与函数指针在某些方面类似,但是它具备更大的安全性和灵活性。所有的委托(Delegate)都派生自 System.Delegate 类
委托的声明和使用
- 声明一个委托时,需要定义其类型签名。例如:delegate int Calculate(int x, inty);
- 使用:
static int Add(int x, int y){ return x + y;} static void Main(string[] args){ Calculate calc = new Calculate(Add); int result = calc(3, 4); // returns 7 }
委托链:C#中的委托也支持委托链,即将多个委托合并在一起以便同时调用。例如将两个委托合并可以使用+运算符,而将一个委托从另一个委托中删除则使用-运算符。
- 例如:
static int Add(int x, int y){ return x + y; } static int Multiply(int x, int y){ return x * y; } static void Main(string[] args){ Calculate calc1 = new Calculate(Add); Calculate calc2 = new Calculate(Multiply); Calculate calcMixed = calc1 + calc2; int result1 = calc1(3, 4); // returns 7 int result2 = calc2(3, 4); // returns 12 int result3 = calcMixed(3, 4); // returns 15 }
泛型委托
- C# 还支持泛型委托,这是一种定义能够引用任意方法(无论其签名)的强类型模板。
- 语法:delegate TResult Func
(T arg);
- 它有一个输入参数 T 和输出结果 TResult,并且可以引用任何带有 T 类型输入和 TResult 类型输出的方法。
委托中的Lambda表达式
- 使代码更加简洁
- 例如
static void Main(string[] args) { Calculate calc1 = (x, y) => x + y; Calculate calc2 = (x, y) => x * y; int result1 = calc1(3, 4); // returns 7 int result2 = calc2(3, 4); // returns 12 }
使用委託代替Action類型
- 使用委托代替 `Action` 类型也是一种常见的做法。以下是使用委托的示例:
public delegate void SomeMethodDelegate(); public void DoSomethingBasedOnCondition(bool condition) { var groups = new[] { new { Condition = true, Action = new SomeMethodDelegate(SomeMethod1) }, new { Condition = false, Action = new SomeMethodDelegate(SomeMethod2) } }; var matchingGroup = groups.FirstOrDefault(g => g.Condition == condition); if (matchingGroup != null) { matchingGroup.Action.Invoke(); } } private void SomeMethod1() { Console.WriteLine("条件为 true,执行方法1"); } private void SomeMethod2() { Console.WriteLine("条件为 false,执行方法2"); }
- C# 中使用事件机制实现线程间的通信。
事件委托:
- 事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
- 发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
- 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
- 事件声明
- 首先在类的内部声明事件,首先必须声明该事件的委托类型。例如:
- public delegate void BoilerLogHandler(string status);
- 然后,声明事件本身,使用 event 关键字:
- public event BoilerLogHandler BoilerEventLog;
- 例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyWork.Delegate { class DelegateTest { static void Main(string[] args) { Publishser pub = new Publishser(); Subscriber sub = new Subscriber(); pub.NumberChanged += new NumberChangedEventHandler(sub.OnNumberChanged); pub.DoSomething(); // 应该通过DoSomething()来触发事件 // pub.NumberChanged(100); // 但可以被这样直接调用,对委托变量的不恰当使用 } } /// /// 定义委托 /// /// public delegate void NumberChangedEventHandler(int count); /// /// 定义事件发布者 /// public class Publishser { private int count; // public NumberChangedEventHandler NumberChanged; // 声明委托变量 public event NumberChangedEventHandler NumberChanged; // 声明一个事件 public void DoSomething() { // 在这里完成一些工作 ... if (NumberChanged != null) // 触发事件 { count++; NumberChanged(count); } } } /// /// 定义事件订阅者 /// public class Subscriber { public void OnNumberChanged(int count) { Console.WriteLine("Subscriber notified: count = {0}", count); } } }
Observer设计模式
- Observer是一种常见的设计模式,在该模式中,对象维护一个依赖于它的观察者列表,当对象自身发生变化时,会通知所有观察者进行更新。
- 具体来说,Observer模式包含两个主要角色:Subject和Observer。Subject是被观察者,负责维护所有注册了它的Observer的列表,并实现了对这些Observer的管理方法(例如添加、删除、通知)。Observer则是观察者,负责实现在Subject发生变化时所需进行的行为。
- 使用Observer模式可以实现对象间的松耦合,提高代码的可维护性和扩展性。同时,Observer模式也能使对象之间的关系更加清晰易懂,增强代码的可读性。
- 在实际应用中,Observer模式经常被用来处理一些动态的事件或状态变化,例如用户界面上的按钮点击事件、网络连接状态的变化等等。
- 例如:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MyWork.Delegate { public class BoilWaterTest { static void Main(string[] args) { Heater heater = new Heater(); Alarm alarm = new Alarm(); heater.BoilEvent += alarm.MakerAlert; heater.BoilEvent += (new Alarm()).MakerAlert;//给匿名对象注册方法 heater.BoilEvent += Display.MakerDisplay; heater.BoilWater(); } } public class Heater { private int temperature;//温度 public delegate void BoilHandler(int param); public event BoilHandler BoilEvent; public void BoilWater() { for(int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) { if (BoilEvent != null) { BoilEvent(temperature);//调用所有注册对象的方法 } } } } } public class Alarm { public void MakerAlert(int param) { Console.WriteLine("滴滴滴!水烧开了!当前{0}度", param); } } public class Display { public static void MakerDisplay(int param) { Console.WriteLine("水快烧开了!当前{0}度", param); } } }
.NET 框架中的委托与事件
- 编码规范:
- 1. 委托类型的名称都应该以 EventHandler 结束。
- 2. 委托的原型定义:有一个void 返回值,并接受两个输入参数:一个Object 类型,一个EventArgs 类型(或继承自EventArgs)。
- 3. 事件的命名为委托去掉 EventHandler 之后剩余的部分。
- 4. 继承自 EventArgs 的类型应该以EventArgs 结尾。
- 示例代码:
- 委托类:
public class Heater { private int temperature; public string type = "RealFire 001"; // 添加型号作为演示 public string area = "China Xian"; // 添加产地作为演示 public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e); public event BoiledEventHandler Boiled; // 声明事件 // 定义 BoiledEventArgs 类,传递给 Observer 所感兴趣的信息 public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temperature) { this.temperature = temperature; } } // 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视 protected virtual void OnBoiled(BoiledEventArgs e) { if (Boiled != null) { Boiled(this, e); // 调用所有注册对象的方法 } } public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) { // 建立BoiledEventArgs 对象。 BoiledEventArgs e = new BoiledEventArgs(temperature); OnBoiled(e); // 调用 OnBolied 方法 } } } }
- 测试:
public class Alarm { public void MakeAlert(Object sender, Heater.BoiledEventArgs e) { Heater heater = (Heater)sender; // 这里是不是很熟悉呢? // 访问 sender 中的公共字段 Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature); Console.WriteLine(); } } public class Display { public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) // 静态方法 { Heater heater = (Heater)sender; Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature); Console.WriteLine(); } } class Program { static void Main() { Heater heater = new Heater(); Alarm alarm = new Alarm(); // heater.Boiled += alarm.MakeAlert; //注册方法 // heater.Boiled += (new Alarm()).MakeAlert; //给匿名对象注册方法 //heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册 // heater.Boiled += Display.ShowMsg; //注册静态方法 heater.BoilWater(); //烧水,会自动调用注册过对象的方法 } }
委托和方法的异步调用
- AsyncResult :
public delegate int AddDelegate(int x, int y); class AsyncTest { static void Main(string[] args) { Console.WriteLine("Client application started!\n"); Thread.CurrentThread.Name = "Main Thread"; Calculator cal = new Calculator(); AddDelegate del = new AddDelegate(cal.Add); IAsyncResult asyncResult = del.BeginInvoke(2, 5, null, null); // 异步调用方法 // 做某些其它的事情,模拟需要执行3 秒钟 for (int i = 1; i <= 3; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}: Client executed {1} second(s).", Thread.CurrentThread.Name, i); } int rtn = del.EndInvoke(asyncResult); Console.WriteLine("Result: {0}\n", rtn); Console.WriteLine("\nPress any key to exit..."); Console.ReadLine(); } } public class Calculator { public int Add(int x, int y) { if (Thread.CurrentThread.IsThreadPoolThread) { Thread.CurrentThread.Name = "Pool Thread"; } Console.WriteLine("Method invoked!"); // 执行某些事情,模拟需要执行2 秒钟 for (int i = 1; i <= 2; i++) { Thread.Sleep(TimeSpan.FromSeconds(i)); Console.WriteLine("{0}: Add executed {1} second(s).", Thread.CurrentThread.Name, i); } Console.WriteLine("Method complete!"); return x + y; } }
- 在示例代码中,BeginInvoke() 方法的最后两个参数分别是 AsyncCallback 回调函数和一个用户定义的对象。这两个参数可以方便地向异步操作传递额外的信息。
- 第一个参数 AsyncCallback 是一个委托类型,用于指向一个函数,该函数将在异步操作完成时自动调用。该委托函数包含一个 IAsyncResult 参数,以便接收异步操作的状态信息。
- 在示例代码中,我们并未设置回调函数,因此传递了一个 null 值。如果需要的话,也可以创建一个名为 MyCallback 的方法,并在 BeginInvoke() 方法中传递该方法名作为回调函数。
- 第二个参数是用户定义的对象(UserState),可用于存储任何数据对象。当异步操作完成时,执行回调函数时,该值会传递给回调函数,从而使得回调函数能够访问其内容。
- 在示例代码中,我们同样使用了 null 值。如果需要为异步操作传递一些附加数据,则可以将它们作为第二个参数传递到 BeginInvoke() 方法中。
- iAyncResult.AsyncWaitHandle.WaitOne(500, false)方法说明
- 这段代码是在等待异步操作完成。具体来说,它会等待异步操作的WaitHandle对象信号被触发,或者等待一定的时间间隔后超时返回。
- 参数500表示最长等待时间为500毫秒,如果在这段时间内等待句柄没有被触发,就会返回;第二个参数false表示不将等待句柄重置,即使之前已经有其他线程等待过它了,当前线程依然可以等待该句柄。
- 实现多线程的几种方式
使用Thread类
Thread类是C#中最基本的多线程实现方式,需要手动管理线程的生命周期和线程同步机制。
using System; using System.Threading; class Program { static void Main() { // 创建线程实例 Thread thread = new Thread(new ThreadStart(DoWork)); // 启动线程 thread.Start(); // 主线程继续执行其他任务 for (int i = 0; i < 10; i++) { Console.WriteLine("Main thread is running."); Thread.Sleep(1000); } // 等待线程结束 thread.Join(); Console.WriteLine("Thread has finished."); } static void DoWork() { for (int i = 0; i < 10; i++) { Console.WriteLine("Worker thread is running."); Thread.Sleep(1000); } } }
使用Task类
Task类是.NET Framework中的新特性,可以更方便地管理线程的生命周期和线程同步机制
using System; using System.Threading.Tasks; class Program { static void Main() { // 创建Task实例 Task task = Task.Factory.StartNew(DoWork); // 主线程继续执行其他任务 for (int i = 0; i < 10; i++) { Console.WriteLine("Main thread is running."); Task.Delay(1000).Wait(); } // 等待Task完成 task.Wait(); Console.WriteLine("Task has finished."); } static void DoWork() { for (int i = 0; i < 10; i++) { Console.WriteLine("Worker thread is running."); Task.Delay(1000).Wait(); } } }
使用ThreadPool类
ThreadPool类是.NET Framework提供的线程池实现,可以更方便地管理线程的生命周期和线程同步机制
using System; using System.Threading; class Program { static void Main() { // 将工作项添加到线程池 ThreadPool.QueueUserWorkItem(DoWork); // 主线程继续执行其他任务 for (int i = 0; i < 10; i++) { Console.WriteLine("Main thread is running."); Thread.Sleep(1000); } Console.WriteLine("Thread has finished."); } static void DoWork(object state) { for (int i = 0; i < 10; i++) { Console.WriteLine("Worker thread is running."); Thread.Sleep(1000); } } }
- 相关方法:
- 1. Thread.Sleep(),它会让执行当前代码的线程暂停一段时间(如果你对线程的概念比较陌生,可以理解为使程序的执行暂停一段时间),以毫秒为单位,比如Thread.Sleep(1000),将会使线程暂停1 秒钟。在上面我使用了它的重载方法,个人觉得使用TimeSpan.FromSeconds(1),可读性更好一些。
- 2. Thread.CurrentThread.Name,通过这个属性可以设置、获取执行当前代码的线程的名称,值得注意的是这个属性只可以设置一次,如果设置两次,会抛出异常。
- 3. Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程
List
List是C#中最常用的集合类之一,可以动态地增加或减少元素。List实现了IList接口,支持随机访问元素。List中的元素可以是任何类型,包括值类型和引用类型。
- 创建List实例
可以使用new关键字创建List实例,需要指定List中元素的类型。以下是创建List实例的示例代码:
List
list1 = new List (); // 创建int类型的空List实例 List list2 = new List (); // 创建string类型的空List实例 List 也可以使用集合初始化器创建List实例,并添加元素。以下是使用集合初始化器创建List实例的示例代码:
List
list1 = new List { 1, 2, 3 }; // 创建包含3个int类型元素的List实例 List list2 = new List { "one", "two", "three" }; // 创建包含3个string类型元素的List实例 List list3 = new List { 1, "two", '3' }; // 创建包含3个object类型元素的List实例 - 添加和删除元素
可以使用Add方法向List中添加元素,使用Remove方法从List中删除指定元素。以下是添加和删除元素的示例代码:
List
list = new List (); list.Add("one"); // 添加元素 list.Add("two"); list.Add("three"); bool result = list.Remove("two"); // 删除元素 foreach (string item in list) { Console.WriteLine(item); } 输出结果为:
one three
也可以使用Insert方法在指定位置插入元素,使用RemoveAt方法删除指定位置的元素。以下是插入和删除元素的示例代码:
List
list = new List (); list.Add("one"); list.Add("three"); list.Insert(1, "two"); // 插入元素 list.RemoveAt(1); // 删除元素 foreach (string item in list) { Console.WriteLine(item); } 输出结果为:
one
- 查找元素
可以使用Contains方法查找List中是否包含指定元素,使用IndexOf方法查找指定元素在List中的位置,使用LastIndexOf方法查找指定元素在List中的最后位置。以下是查找元素的示例代码:
List
list = new List (); list.Add("one"); list.Add("two"); list.Add("three"); list.Add("two"); bool result1 = list.Contains("two"); // 是否包含元素 // result1为true int index1 = list.IndexOf("two"); // 查找元素位置 // index1为1 int index2 = list.LastIndexOf("two"); // 查找元素最后位置 // index2为3 - 排序元素
可以使用Sort方法对List中的元素进行排序。Sort方法接受一个实现IComparer
接口的对象作为比较器,用于比较List中的元素。以下是排序元素的示例代码: List
list = new List { 3, 1, 2 }; list.Sort(); // 排序 foreach (int item in list) { Console.WriteLine(item); } 输出结果为:
1 2 3
也可以使用Sort方法的重载版本,传入一个Lambda表达式作为比较器。以下是使用Lambda表达式排序元素的示例代码:
List
list = new List { "one", "Two", "THREE" }; list.Sort((x, y) => string.Compare(x, y, StringComparison.OrdinalIgnoreCase)); // 忽略大小写排序 foreach (string item in list) { Console.WriteLine(item); } 输出结果为:
复制代码
one THREE Two
- 高级使用
List还支持一些高级使用,包括以下几个方面:
- 缩减List的容量 可以使用TrimExcess方法缩减List的容量,以减少内存占用。以下是缩减List容量的示例代码:
List
list = new List { 1, 2, 3 }; list.TrimExcess(); - 清空List中的元素 可以使用Clear方法清空List中的元素。以下是清空List中元素的示例代码:
List
list = new List { 1, 2, 3 }; list.Clear(); - 将List转换为数组 可以使用ToArray方法将List转换为数组。以下是将List转换为数组的示例代码:
List
list = new List { 1, 2, 3 }; int[] array = list.ToArray(); - 将数组转换为List 可以使用ToList方法将数组转换为List。以下是将数组转换为List的示例代码:
int[] array = { 1, 2, 3 }; List
list = array.ToList(); - 使用Foreach方法遍历List 可以使用Foreach方法遍历List中的元素。Foreach方法接受一个Lambda表达式作为参数,用于处理List中的每个元素。以下是使用Foreach方法遍历List的示例代码:
List
list = new List { "one", "two", "three" }; list.ForEach(item => Console.WriteLine(item)); 输出结果为:
one two three
Dictionary
Dictionary是C#中常用的键值对集合类,可以根据键来访问值。Dictionary实现了IDictionary接口,支持添加、删除、查找和遍历操作。
- 创建Dictionary实例
可以使用new关键字创建Dictionary实例,需要指定键和值的类型。以下是创建Dictionary实例的示例代码:
Dictionary
dict1 = new Dictionary (); // 创建string和int类型的空Dictionary实例 Dictionary dict2 = new Dictionary (); // 创建int和string类型的空Dictionary实例 也可以使用集合初始化器创建Dictionary实例,并添加键值对。以下是使用集合初始化器创建Dictionary实例的示例代码:
Dictionary
dict1 = new Dictionary { { "one", 1 }, { "two", 2 }, { "three", 3 } }; Dictionary dict2 = new Dictionary { { 1, "one" }, { 2, "two" }, { 3, "three" } }; - 添加和删除键值对
可以使用Add方法向Dictionary中添加键值对,使用Remove方法从Dictionary中删除指定键的键值对。以下是添加和删除键值对的示例代码:
Dictionary
dict = new Dictionary (); dict.Add("one", 1); // 添加键值对 dict.Add("two", 2); dict.Add("three", 3); bool result = dict.Remove("two"); // 删除键值对 也可以使用索引器访问和修改Dictionary中的键值对。以下是使用索引器添加和修改键值对的示例代码:
Dictionary
dict = new Dictionary (); dict["one"] = 1; // 添加或修改键值对 dict["two"] = 2; dict["three"] = 3; int value = dict["two"]; // 访问键值对 需要注意的是,如果访问不存在的键,则会抛出KeyNotFoundException异常。可以使用TryGetValue方法来避免抛出异常。以下是使用TryGetValue方法访问键值对的示例代码:
Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("three", 3); int value; bool result = dict.TryGetValue("two", out value); // 此时result为false if (result) { Console.WriteLine(value); } else { Console.WriteLine("Key not found."); } 输出结果为:
Key not found.
- 查找键值对
可以使用ContainsKey方法查找Dictionary中是否包含指定键,使用ContainsValue方法查找Dictionary中是否包含指定值,使用TryGetValue方法获取指定键的值。以下是查找键值对的示例代码:
Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("two", 2); dict.Add("three", 3); bool result1 = dict.ContainsKey("two"); // 是否包含键 // result1为true bool result2 = dict.ContainsValue(2); // 是否包含值 // result2为true int value; bool result3 = dict.TryGetValue("two", out value); // 获取值 // result3为true,value为2 - 遍历键值对
可以使用foreach循环遍历Dictionary中的键值对。foreach循环每次迭代都会返回一个KeyValuePair
结构体,可以使用结构体的Key和Value属性来访问键和值。以下是遍历键值对的示例代码: Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("two", 2); dict.Add("three", 3); foreach (KeyValuePair kvp in dict) { Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, kvp.Value); } 输出结果为:
Key = one, Value = 1 Key = two, Value = 2 Key = three, Value = 3
也可以使用foreach循环遍历Dictionary中的键或值。需要使用字典的Keys或Values属性来访问键或值。以下是遍历键和值的示例代码:
复制代码
Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("two", 2); dict.Add("three", 3); foreach (string key in dict.Keys) { Console.WriteLine("Key = {0}", key); } foreach (int value in dict.Values) { Console.WriteLine("Value = {0}", value); } 输出结果为:
Key = one Key = two Key = three Value = 1 Value = 2 Value = 3
- 高级使用
Dictionary还支持一些高级使用,包括以下几个方面:
- 获取Dictionary中键或值的集合 可以使用Keys属性获取Dictionary中键的集合,使用Values属性获取Dictionary中值的集合。以下是获取键或值的集合的示例代码:
Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("two", 2); dict.Add("three", 3); ICollection keys = dict.Keys; // 获取键的集合 ICollection values = dict.Values; // 获取值的集合 - 将Dictionary转换为数组 可以使用ToArray方法将Dictionary转换为数组。ToArray方法返回一个包含Dictionary中所有键值对的KeyValuePair数组。以下是将Dictionary转换为数组的示例代码:
Dictionary
dict = new Dictionary (); dict.Add("one", 1); dict.Add("two", 2); dict.Add("three", 3); KeyValuePair [] array = dict.ToArray(); // 转换为数组 - 将数组转换为Dictionary 可以使用ToDictionary方法将数组转换为Dictionary。ToDictionary方法接受一个Lambda表达式作为参数,用于从数组元素中提取键和值。以下是将数组转换为Dictionary的示例代码:
int[] array = { 1, 2, 3 }; Dictionary
dict = array.To
LINQ查询
通过使用LINQ查询,可以从Dictionary对象中筛选出符合某些特定条件的元素。下面以一个Dictionary
为例,演示如何使用LINQ查询语法从Dictionary中筛选出所有值大于2的键值对。 Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; var result = from item in dict where item.Value > 2 select item; foreach (var item in result) { Console.WriteLine("{0}: {1}", item.Key, item.Value); } 输出:
three: 3 four: 4
2.Lambda表达式
Lambda表达式是一种用于简洁地定义匿名方法的方式,可以在Collections类的扩展方法中使用。下面以一个Dictionary
为例,演示如何使用Lambda表达式筛选出所有值大于2的键值对: Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; var result = dict.Where(x => x.Value > 2); foreach (var item in result) { Console.WriteLine("{0}: {1}", item.Key, item.Value); } 输出:
three: 3 four: 4
转换操作
除了筛选元素之外,LINQ和Lambda表达式还可以执行一些转换操作,如映射、分组、排序等。下面以一个Dictionary
为例,演示如何使用Lambda表达式对字典中的值进行平方操作: Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; var result = dict.Select(x => new KeyValuePair (x.Key, x.Value * x.Value)); foreach (var item in result) { Console.WriteLine("{0}: {1}", item.Key, item.Value); } 输出:
one: 1 two: 4 three: 9 four: 16
4.Sum、Max和Min操作
在LINQ中,可以使用Sum、Max和Min等聚合操作对集合中的元素进行求和、最大值和最小值等操作。下面以一个Dictionary
为例,演示如何使用LINQ查询语法从Dictionary中计算所有值的和: Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; int sum = (from item in dict select item.Value).Sum(); Console.WriteLine("Sum: {0}", sum); 输出:
Sum: 10
使用Lambda表达式也可以进行相同的操作:
Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; int sum = dict.Values.Sum(); Console.WriteLine("Sum: {0}", sum); 输出:
Sum: 10
5.OrderBy和ThenBy操作
在LINQ中,可以使用OrderBy和ThenBy方法对集合进行排序。OrderBy方法用于对集合进行升序排序,ThenBy方法用于在升序排序的基础上继续对集合进行排序。下面以一个Dictionary
为例,演示如何使用Lambda表达式对字典中的值进行排序: Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; var result = dict.OrderBy(x => x.Value); foreach (var item in result) { Console.WriteLine("{0}: {1}", item.Key, item.Value); } 输出:
one: 1 two: 2 three: 3 four: 4
可以使用ThenBy方法对排序进行进一步的指定:
Dictionary
dict = new Dictionary { {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4} }; var result = dict.OrderBy(x => x.Value).ThenBy(x => x.Key); foreach (var item in result) { Console.WriteLine("{0}: {1}", item.Key, item.Value); } 输出:
one: 1 two: 2 three: 3 four: 4
Queue
C#中的Queue是一个基于先进先出(FIFO)原则的集合类,用于存储同一类型对象的集合。Queue类有两种实现方式:基于数组的实现和基于链表的实现。Queue类提供了一系列方法,可以用于在队列中添加、删除、查询元素,以及确定队列中元素的数量等操作。
1.创建和初始化Queue对象
首先需要创建一个Queue对象,并进行初始化,以便向队列中添加元素。下面是一个基于数组的Queue初始化的示例代码:
Queue
queue = new Queue (); 可以使用Add或Enqueue方法向队列中添加元素,例如:
queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(3);
也可以使用数组的方式进行初始化:
Queue
queue = new Queue (new[] { 1, 2, 3 }); 2.添加元素
可以使用Enqueue方法向队列中添加元素,如:
queue.Enqueue(4); queue.Enqueue(5);
3.获取元素
可以使用Dequeue方法从队列中获取并删除第一个元素,如:
int first = queue.Dequeue(); Console.WriteLine(first);
也可以使用Peek方法获取并保留第一个元素,如:
int first = queue.Peek(); Console.WriteLine(first);
4.查询元素
可以使用Contains方法查询元素是否存在于队列中,如:
bool exists = queue.Contains(3); Console.WriteLine(exists);
也可以使用ToArray方法将队列中的所有元素复制到一个数组中:
int[] array = queue.ToArray(); foreach (int i in array) { Console.WriteLine(i); }
5.队列的长度
可以使用Count属性获取队列中的元素数量,如:
int count = queue.Count; Console.WriteLine(count);
6.迭代器
Queue也支持迭代器,可以使用foreach语句遍历Queue中的元素,如:
foreach (int i in queue) { Console.WriteLine(i); }
7.清空队列
可以使用Clear方法清空队列中的所有元素:
queue.Clear();
高级使用:
1.使用自定义对象
可以使用自定义类型作为Queue中的元素类型。只需要确保自定义类型实现了相应的接口(例如IComparable或IEquatable)即可。例如:
class Person : IComparable
{ public string Name { get; set; } public int Age { get; set; } public int CompareTo(Person other) { return this.Age.CompareTo(other.Age); } } Queue queue = new Queue (); queue.Enqueue(new Person { Name = "Tom", Age = 20 }); queue.Enqueue(new Person { Name = "Mary", Age = 25 }); 2.使用委托自定义比较器
可以使用委托来自定义比较器,例如需要按照Person对象的Name属性进行排序:
class Person { public string Name { get; set; } public int Age { get; set; } } Queue
queue = new Queue ((x, y) => String.Compare(x.Name, y.Name)); queue.Enqueue(new Person { Name = "Tom", Age = 20 }); queue.Enqueue(new Person { Name = "Mary", Age = 25 }); 3.使用Queue与Stack结合实现栈
Queue和Stack都是一种基于集合的数据结构,它们的实现机制不同,但用法非常类似。可以使用Queue和Stack结合实现栈的功能,例如:
class Stack
{ private Queue queue = new Queue (); public void Push(T item) { queue.Enqueue(item); int count = queue.Count; while (count-- > 1) { queue.Enqueue(queue.Dequeue()); } } public T Pop() { return queue.Dequeue(); } public int Count { get { return queue.Count; } } } Stack stack = new Stack (); stack.Push(1); stack.Push(2); stack.Push(3); Console.WriteLine(stack.Pop()); // 3 Console.WriteLine(stack.Pop()); // 2 Console.WriteLine(stack.Pop()); // 1
Stack
C#中的Stack是一种基于后进先出(LIFO)原则的集合类,用于存储同一类型对象的集合。Stack类有两种实现方式:基于数组的实现和基于链表的实现。Stack类提供了一系列方法,可以用于在栈中添加、删除、查询元素,以及确定栈中元素的数量等操作
1.创建和初始化Stack对象
首先需要创建一个Stack对象,并进行初始化,以便向栈中添加元素。下面是一个基于数组的Stack初始化的示例代码:
Stack
stack = new Stack (); 也可以使用数组的方式进行初始化:
Stack
stack = new Stack (new[] { 1, 2, 3 }); 2.添加元素
可以使用Push方法向栈中添加元素,如:
stack.Push(4); stack.Push(5);
3.获取元素
可以使用Pop方法从栈中获取并删除最后一个元素,如:
int last = stack.Pop(); Console.WriteLine(last);
也可以使用Peek方法获取并保留最后一个元素,如:
int last = stack.Peek(); Console.WriteLine(last);
4.查询元素
可以使用Contains方法查询元素是否存在于栈中,如:
bool exists = stack.Contains(3); Console.WriteLine(exists);
也可以使用ToArray方法将栈中的所有元素复制到一个数组中:
int[] array = stack.ToArray(); foreach (int i in array) { Console.WriteLine(i); }
5.栈的长度
可以使用Count属性获取栈中的元素数量,如:
int count = stack.Count; Console.WriteLine(count);
6.迭代器
Stack也支持迭代器,可以使用foreach语句遍历Stack中的元素,如:
foreach (int i in stack) { Console.WriteLine(i); }
7.清空栈
可以使用Clear方法清空栈中的所有元素:
stack.Clear();
高级使用:
1.使用自定义对象
可以使用自定义类型作为Stack中的元素类型。只需要确保自定义类型实现了相应的接口(例如IComparable或IEquatable)即可。例如:
class Person : IComparable
{ public string Name { get; set; } public int Age { get; set; } public int CompareTo(Person other) { return this.Age.CompareTo(other.Age); } } Stack stack = new Stack (); stack.Push(new Person { Name = "Tom", Age = 20 }); stack.Push(new Person { Name = "Mary", Age = 25 }); 2.使用委托自定义比较器
可以使用委托来自定义比较器,例如需要按照Person对象的Name属性进行排序:
class Person { public string Name { get; set; } public int Age { get; set; } } Stack
stack = new Stack ((x, y) => String.Compare(x.Name, y.Name)); stack.Push(new Person { Name = "Tom", Age = 20 }); stack.Push(new Person { Name = "Mary", Age = 25 }); 3.使用Stack与Queue结合实现队列
Stack和Queue都是一种基于集合的数据结构,它们的实现机制不同,但用法非常类似。可以使用Stack和Queue结合实现队列的功能,例如:
ti
class Queue
{ private Stack stack1 = new Stack (); private Stack stack2 = new Stack (); public void Enqueue(T item) { stack1.Push(item); } public T Dequeue() { if (stack2.Count == 0) { while (stack1.Count > 0) { stack2.Push(stack1.Pop()); } } return stack2.Pop(); } public int Count { get { return stack1.Count + stack2.Count; } } } Queue queue = new Queue (); queue.Enqueue(1); queue.Enqueue(2); queue.Enqueue(3); Console.WriteLine(queue.Dequeue()); // 1 Console.WriteLine(queue.Dequeue()); // 2 Console.WriteLine(queue.Dequeue()); // 3
- 其它补充
- Console.ReadKey()
- 在C#中,Console.ReadKey()是一个阻塞方法,它等待用户按下任意键,并返回一个表示所按键的KeyInfo对象。它通常用于控制台程序的调试和交互过程中。
- 下面是使用Console.ReadKey()的示例:
using System; class Program { static void Main(string[] args) { Console.WriteLine("按下任意键继续..."); Console.ReadKey(); Console.WriteLine("您已按下键!"); } }
- 在上面的示例中,程序在控制台输出"按下任意键继续...",并等待用户按键。当用户按下键后,控制台将输出"您已按下键!"。
- 需要注意的是,Console.ReadKey()方法在读取到按键后会将该键从输入缓冲区中删除。如果需要保留输入缓冲区的内容,请使用Console.Read()方法。
- abstract和virtual的区别:
- abstract可以修饰类和方法,子类继承基类时,必须实现基类使用abstract所修饰的方法
- virtual只能修饰方法,且不要求子类一定实现基类使用virtual所修饰的方法
- Visual studio工具
- 调试快捷键:
- F5:开始调试
- shift+F5:停止调试
- F9:设置或取消断点
- Ctrl+F9:取消断点
- F10:单步执行
- F2 : 转到所调用过程或变量的定义
- Ctrl+F2: 将焦点转移到类的下拉列表框
- 编码快捷键:
- 封装字段:Ctrl+R+E