C#知识点概要
1.方法重载和重写重载在同一个类中定义多个同名但形参不同的方法。重写通过使用virtual与override关键字实现基类与派生类方法内容不同。注抽象类必须派生类必须重写虚方法均可。2.面向对象三大特征1.封装将属性和方法结合并限制外部直接访问数据能力。保护对象的内部状态同时提供公共方法让外部访问增强数据安全性。2.继承作用提高代码重复利用率增强可维护性。将子类的公共属性集合在一起方便共同管理。特性传递性与单根性3.多态通过统一的接口调用不同类的对象从而实现统一操作的不同结果主要是重载和重写。3.值类型和引用类型1. 值类型存储方式直接存储实际数据。包含类型intfloatboolchardoublestructenum 等。存储位置栈内存。2.引用类型存储方式存储数据的引用内存地址栈内存 实际数据堆内存。包含类型stringobjectclassinterfacedelegate。存储位置堆内存。4.堆与栈1.栈存储内容值类型数据引用地址函数调用中的传入参数局部变量与返回地址等。生命周期随作用域自动创建与销毁。物理内存地址连续且无内存碎片的问题。2.堆存储内容引用类型实际数据如new关键词创建的对象等。生命周期由GC决定销毁时机只要有引用指向该对象就会继续存在。物理内存地址分散且有内存碎片的问题。5.装箱与拆箱1.装箱将值类型转换为object类型。2.拆箱object类型转换为值类型。3.触发时机将值类型赋值给变量或接口变量。调用object或接口方法传入值类型参数。非泛型集合存储值类型ArrayList等。4.避免不必要的装拆箱使用泛型集合ListT与方法。避免将值类型转化为object。避免在Unity周期函数中拆装箱。使用NativeArrayT处理高频值类型数据。6.privatepublic与protected1.public对任何类和成员公开无限制访问。2.private仅对该类公开。3.protected对该类及其派生类公开。7.ArrayList与List1.ArrayList非泛型集合存储的数据类型为Object引用类型可存储任何类型的对象但使用时会进行类型转换装/拆箱。2.List泛型集合提供类型参数T避免类型转换更加高效安全。8.接口与抽象类1.接口完全抽象类型只定义不实现。多继承成员只能是public。定义的功能能被多个类实现不同的类实现不同的效果这样调用不同类的相同接口方法就可以执行不同的效果。2.抽象类包含抽象方法不实现与具体方法实现单继承成员可以是publicprotectedprivate不可以修饰抽象方法。子类可以继承并重复使用父类的具体方法也必须让子类重写抽象类实现特定方法。注虚方法有默认实现子类可以选择性覆写Override可以在抽象类或普通类中。抽象方法无默认实现子类必须覆写且只能在抽象类中。9.Sealed关键字类声明时可防止其他类继承此类在方法中声明则可防止派生类重写此方法。10.unsafe关键字作用启用指针操作允许开发者直接操作内存地址。用法修饰代码块方法类和结构体。11.ref与out关键字1.ref关键字修饰引用参数调用前必须初始化。作用利用变量现有值并在使用后更改。示例void AddOne(ref int num) { num; } int x 5; AddOne(ref x); Console.WriteLine(x); // 输出6原变量被修改2.out关键字修饰输出参数调用前无需初始化。作用强制输出多结果。示例bool TryParseInt(string input, out int result) { if (int.TryParse(input, out result)) { return true; } result 0; return false; } string str 123; if (TryParseInt(str, out int num)) { Console.WriteLine(num); // 输出123方法为num赋值 }else { Console.WriteLine(num); // 输出0 }3.注意事项1引用参数和输出参数不会创建新的存储位置。2普通参数传递的是地址的副本而ref传递的是变量本身的引用修改会直接影响实参。12.结构体与类1.结构体值类型赋值/传递时会创建一个副本不能定义无参构造只能定义全参构造。用法常作为数据容器。2.类引用类型赋值/传递时会复制引用地址可以定义无参构造。用法常用于复杂行为实例较多需要继承或被继承。13.构造函数1.概念一种名字与类或结构体相同的方法用于创建对象时执行。2.常见类型默认若类中未定义构造函数C#会自动生成一个无参数的默认构造函数。有参public class Person { public string Name; public Person(string name, int age) { Name name; } }重载一个类可定义多个构造函数参数不同。静态static修饰且无参用于初始化类的静态成员由系统自动调用仅在类被使用时执行一次3.注意事项构造函数属于当前类不能被继承非静态构造函数在每次对象实例化都会执行。14.泛型1.概念定义类接口与方法时使用的类型占位符的一种机制。2.常见类型类public class BoxT { public T Value; public void SetValue(T value) { Value value; } public T GetValue() { return Value; } }Boxint intBox new Boxint(); intBox.SetValue(100); int num intBox.GetValue();方法即使类不是泛型也可以定义泛型方法。接口public interface IIterableT { bool HasNext(); T Next(); }3.泛型约束where T : classT必须是引用类型如string、classwhere T : structT必须是值类型如int、structwhere T : new()T必须有公共无参构造函数15.泛型容器与非泛型容器1.泛型容器ListT,DictionaryTkey,TValue,QueueT,StackT。2.非泛型容器ArrayListHashtable哈希表QueueStack。3.常见容器ListT存储有序可重复元素动态扩容。按索引访问速度快插入删除效率低。HashSetT存储不重复元素快速判断元素是否存在。Queue队列先进先出顺序处理数据。Stack栈先进后出顶部添加顶部移除。17.字典Dictionary内部实现原理1)概念基于哈希表实现的键值对集合。2)内部核心结构buckets数组作为哈希表的“桶”存储entries数组的索引初始长度为最小质数动态扩容翻倍为下一质数。buckets[哈希值索引] entries数组中的索引。entries数组存储实际键值对每一个元素是一个结构体键值键的哈希码下一索引辅助字段count键值对数量version版本号freeList空闲条目索引freeCount空闲条目数量3)哈希冲突的处理哈希冲突当两个及以上不同键计算出相同的桶的索引。处理办法将桶中的条目形成链表也就是一个桶多个条目而桶本身存储的是索引对应的条目是链表的头通过Next下一索引将其串联。4)关键操作扩容当哈希表的负载因子元素数量/桶的数量超过阈值会自动扩容重新计算所有元素的哈希值分配到新桶通过增加桶的数量减少单个桶中链表的长度维持效率。查找从对应的桶的第一个条目开始遍历链表比较哈希码再比较键都对应返回值否则返回false注条目即为键值对桶存储的数据是entries数组的索引而buckets数组的索引是哈希值entries数组存储的是键值对。18.C#与C中的哈希表1. 底层实现1C# Hashtable基于拉链法Separate Chaining实现每个桶Bucket是一个链表。使用双散列解决哈希冲突。非泛型键值类型为object存在装箱拆箱开销。已被泛型DictionaryTKey, TValue取代但在旧代码中仍可见。2Cstd::unordered_map基于开放寻址法Open Addressing或拉链法不同编译器实现不同。泛型键值类型在编译时确定无类型转换开销。通常通过模板实现内存布局更紧凑。2. 线程安全性1C# Hashtable默认非线程安全但可通过Hashtable.Synchronized方法创建线程安全版本。线程安全版本使用锁机制性能较低。2Cstd::unordered_map标准库实现非线程安全需用户自行加锁如std::mutex。C11 后支持std::unordered_map的并发版本如std::unordered_mapstd::shared_mutex。3. 内存管理1C# Hashtable由 .NET 垃圾回收器GC自动管理内存无需手动释放。2Cstd::unordered_map需手动管理内存如插入/删除时构造/析构对象。4. 性能对比特性C# HashtableCstd::unordered_map查找时间复杂度O(1)平均O(1)平均内存开销较高链表节点 装箱开销较低连续内存布局线程安全支持内置但性能低需手动实现19.元数据与反射1)元数据概念描述程序集内部所有结构信息的数据表。注反射API的核心就是读取元数据。2)反射概念一种允许程序在运行时获取和操作类型信息类方法对象和数据的机制。核心类1.Type表示类型的元数据是反射的 “入口”包含类的所有信息。2.Assembly表示程序集用于加载程序集并获取其中的类型。3.MethodInfo表示方法的元数据用于动态调用方法。4.PropertyInfo表示属性的元数据用于动态访问 / 修改属性。5.FieldInfo表示字段的元数据用于动态访问 / 修改字段。示例public class Person { public string Name;// 字段 public int Age { get; set; }// 属性 public int Add(int a, int b) { return a b; } }class ReflectionDemo { static void Main() { // 获取Type对象反射的入口 Type personType typeof(Person); // 获取所有公共属性 PropertyInfo[] properties personType.GetProperties(); foreach (var prop in properties) { Console.WriteLine($- 属性{prop.Name}类型{prop.PropertyType.Name}); } // 获取所有公共方法 MethodInfo[] methods personType.GetMethods(); foreach (var method in methods) { // 过滤掉从object继承的方法如ToString、GetHashCode等 if (method.DeclaringType personType) { Console.WriteLine($- 方法{method.Name}); } } // 3. 动态创建对象无需显式new Person() Person person (Person)Activator.CreateInstance(personType); // 调用无参构造函数 // 给字段赋值 FieldInfo nameField personType.GetField(Name); nameField.SetValue(person, Alice); // 等价于 person.Name Alice // 给属性赋值 PropertyInfo ageProp personType.GetProperty(Age); ageProp.SetValue(person, 25); // 等价于 person.Age 25 // 调用带参数的方法Add MethodInfo addMethod personType.GetMethod(Add); object result addMethod.Invoke(person, new object[] { 3, 5 }); // 等价于 person.Add(3,5) Console.WriteLine($3 5 {result}); // 输出 8 } }作用分析类型结构动态创建对象、调用方法、访问与修改属性和字段加载并使用编译时未知的程序集插件。20.forforeach与Enumerator1.for循环通过索引访问集合元素。可通过索引直接访问和修改集合元素。2.foreach循环依赖枚举器Enumerator实现遍历无需索引。不可以修改添加/删除集合元素。3.Enumerator枚举器接口本身。foreach循环的底层实现获取枚举器对象任何集合类对象均有GetEnumerator()调用MoveNext()移动枚举器Current属性获取当前元素遍历结束自动调用Dispose()释放资源。注这个枚举器对象不是集合类对象而是一个独立的类对象。21.C#编译过程中的文件与概念1.Unity中C#生命过程编写C#源代码编译编译器将源代码编译为IL与元数据并打包成程序集文件分发/部署程序集文件被发布执行IL2CPP工具将IL与元数据转换为C源代码使用编译器将C源代码编译成本地机器码链接成本地可执行文件在目标设备上直接执行本地机器码。22.小知识点总结1A实例化赋给B将B删除A是否存在答存在B只是复制A栈中的地址A的实例在堆中没有改变。2Foreach循环迭代时若把其中的某个元素删除程序报错怎么处理答由于foreach不能删除元素因此需要记录找到索引或key值迭代结束后再进行删除。3函数中多次使用string的处理会产生大量内存垃圾有什么好的方法可以解决答使用StringBuilder高效拼接字符串不会产生临时对象。StringBuilder sb new StringBuilder(100000); // 预估容量减少扩容 for (int i 1; i 10000; i) { sb.Append(i).Append(, ); }4当需要频繁创建使用某个对象时有什么好的程序设计方案来节省内存答单例模式或者对象池。5单例模式违反了什么设计原则答违反了依赖倒置原则。