C#知识点概要

1.方法重载和重写

重载:在同一个类中定义多个同名但形参不同的方法。

重写:通过使用virtual与override关键字,实现基类与派生类方法内容不同。

注:抽象类必须派生类必须重写,虚方法均可。

2.面向对象三大特征

1.封装:将属性和方法结合,并限制外部直接访问数据能力。保护对象的内部状态,同时提供公共方法让外部访问,增强数据安全性。

2.继承

  • 作用:提高代码重复利用率,增强可维护性。将子类的公共属性集合在一起,方便共同管理。
  • 特性:传递性与单根性

3.多态:通过统一的接口调用不同类的对象,从而实现统一操作的不同结果,主要是重载重写

3.值类型和引用类型

  1. 值类型
  • 存储方式:直接存储实际数据。
  • 包含类型:int,float,bool,char,double,struct,enum 等。
  • 存储位置:栈内存。

2.引用类型

  • 存储方式:存储数据的引用(内存地址)(栈内存)+ 实际数据(堆内存)。
  • 包含类型:string,object,class,interface,delegate。
  • 存储位置:堆内存。

4.堆与栈

1.栈

  • 存储内容:值类型数据,引用(地址),函数调用中的传入参数,局部变量与返回地址等。
  • 生命周期:随作用域自动创建与销毁。
  • 物理内存:地址连续且无内存碎片的问题

2.堆

  • 存储内容:引用类型实际数据(如new关键词创建的对象等)。
  • 生命周期:由GC决定销毁时机,只要有引用指向该对象,就会继续存在。
  • 物理内存:地址分散且有内存碎片的问题

5.装箱与拆箱

1.装箱:将值类型转换为object类型。

2.拆箱:object类型转换为值类型。

3.触发时机:

  • 将值类型赋值给变量或接口变量。
  • 调用object或接口方法,传入值类型参数。
  • 非泛型集合存储值类型(ArrayList等)。

4.避免不必要的装拆箱

  • 使用泛型集合(List<T>)与方法。
  • 避免将值类型转化为object。
  • 避免在Unity周期函数中拆装箱。
  • 使用NativeArray<T>处理高频值类型数据。

6.private,public与protected

1.public:对任何类和成员公开,无限制访问。

2.private:仅对该类公开。

3.protected:对该类及其派生类公开。

7.ArrayList与List

1.ArrayList:非泛型集合,存储的数据类型为Object(引用类型),可存储任何类型的对象,但使用时会进行类型转换(装/拆箱)。

2.List:泛型集合,提供类型参数T,避免类型转换,更加高效安全。

8.接口与抽象类

1.接口

  • 完全抽象类型,只定义不实现。
  • 多继承,成员只能是public。
  • 定义的功能能被多个类实现,不同的类实现不同的效果,这样调用不同类的相同接口方法,就可以执行不同的效果。

2.抽象类

  • 包含抽象方法(不实现)与具体方法(实现)
  • 单继承,成员可以是public,protected,private(不可以修饰抽象方法)。
  • 子类可以继承并重复使用父类的具体方法,也必须让子类重写抽象类实现特定方法。

注:

  • 虚方法:有默认实现,子类可以选择性覆写(Override),可以在抽象类或普通类中。
  • 抽象方法:无默认实现,子类必须覆写,且只能在抽象类中。

9.Sealed关键字

类声明时可防止其他类继承此类,在方法中声明则可防止派生类重写此方法。

10.unsafe关键字

  • 作用:启用指针操作,允许开发者直接操作内存地址。
  • 用法:修饰代码块,方法,类和结构体。

11.ref与out关键字

1.ref关键字

  • 修饰引用参数,调用前必须初始化。

  • 作用:利用变量现有值,并在使用后更改。

  • 示例:

    cs 复制代码
    void AddOne(ref int num)
    {
        num++;
    }
    
    int x = 5;
    AddOne(ref x);
    Console.WriteLine(x); // 输出6(原变量被修改)

2.out关键字

  • 修饰输出参数,调用前无需初始化。

  • 作用:强制输出多结果。

  • 示例:

    cs 复制代码
    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#会自动生成一个无参数的默认构造函数。

  • 有参:

    cs 复制代码
    public class Person
    {
        public string Name;
    
        public Person(string name, int age)
        {
            Name = name;
        }
    }
  • 重载:一个类可定义多个构造函数,参数不同。

  • 静态:static修饰且无参,用于初始化类的静态成员,由系统自动调用(仅在类被使用时执行一次)

3.注意事项:构造函数属于当前类,不能被继承,非静态构造函数在每次对象实例化都会执行。

14.泛型

1.概念:定义类,接口与方法时使用的类型占位符的一种机制。

2.常见类型

  • 类:

    cs 复制代码
    public class Box<T>
    {
        public T Value;
    
        public void SetValue(T value)
        {
            Value = value;
        }
    
        public T GetValue()
        {
            return Value;
        }
    }
    cs 复制代码
    Box<int> intBox = new Box<int>();
    intBox.SetValue(100);
    int num = intBox.GetValue(); 
  • 方法:即使类不是泛型,也可以定义泛型方法。

  • 接口:

    cs 复制代码
    public interface IIterable<T>
    {
        bool HasNext();
        T Next();
    }

3.泛型约束:

|------------------|-------------------------------|
| where T : class | T必须是引用类型(如stringclass) |
| where T : struct | T必须是值类型(如intstruct) |
| where T : new() | T必须有公共无参构造函数 |

15.泛型容器与非泛型容器

1.泛型容器:List<T>,Dictionary<Tkey,TValue>,Queue<T>,Stack<T>。

2.非泛型容器:ArrayList,Hashtable(哈希表),Queue,Stack。

3.常见容器

  • List<T>:存储有序,可重复元素,动态扩容。按索引访问速度快,插入删除效率低。
  • HashSet<T>:存储不重复元素,快速判断元素是否存在。
  • 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. 底层实现

1)C# Hashtable:

  • 基于 拉链法(Separate Chaining)实现,每个桶(Bucket)是一个链表。
  • 使用 双散列 解决哈希冲突。
  • 非泛型 ,键值类型为 object,存在装箱拆箱开销。
  • 已被泛型 Dictionary<TKey, TValue> 取代,但在旧代码中仍可见。

2)C++ std::unordered_map

  • 基于 开放寻址法(Open Addressing)或拉链法(不同编译器实现不同)。
  • 泛型,键值类型在编译时确定,无类型转换开销。
  • 通常通过模板实现,内存布局更紧凑。

2. 线程安全性

1)C# Hashtable:

  • 默认非线程安全 ,但可通过 Hashtable.Synchronized 方法创建线程安全版本。
  • 线程安全版本使用锁机制,性能较低。

2)C++ std::unordered_map

  • 标准库实现非线程安全需用户自行加锁 (如 std::mutex)。
  • C++11 后支持 std::unordered_map 的并发版本(如 std::unordered_map + std::shared_mutex)。

3. 内存管理

1)C# Hashtable:由 .NET 垃圾回收器(GC)自动管理内存,无需手动释放。

2)C++ std::unordered_map:需手动管理内存(如插入/删除时构造/析构对象)。

4. 性能对比

特性 C# Hashtable C++ std::unordered_map
查找时间复杂度 O(1)(平均) O(1)(平均)
内存开销 较高(链表节点 + 装箱开销) 较低(连续内存布局)
线程安全支持 内置(但性能低) 需手动实现

19.元数据与反射

1)元数据

  • 概念:描述程序集内部所有结构信息的数据表。
  • 注:反射API的核心就是读取元数据。

2)反射

  • 概念:一种允许程序在运行时获取和操作类型信息(类,方法,对象和数据)的机制。
  • 核心类:

1.Type:表示类型的元数据(是反射的 "入口",包含类的所有信息)。

2.Assembly:表示程序集,用于加载程序集并获取其中的类型。

3.MethodInfo:表示方法的元数据,用于动态调用方法。

4.PropertyInfo:表示属性的元数据,用于动态访问 / 修改属性。

5.FieldInfo:表示字段的元数据,用于动态访问 / 修改字段。

  • 示例:

    cs 复制代码
    public class Person
    {
        public string Name;// 字段
        
        public int Age { get; set; }// 属性
        
        public int Add(int a, int b)
        {
            return a + b;
        }
    }
    cs 复制代码
    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.for,foreach与Enumerator

1.for循环

  • 通过索引访问集合元素。
  • 可通过索引直接访问和修改集合元素。

2.foreach循环

  • 依赖枚举器(Enumerator)实现遍历,无需索引。
  • 不可以修改(添加/删除)集合元素。

3.Enumerator

  • 枚举器接口本身。
  • foreach循环的底层实现:获取枚举器对象(任何集合类对象均有GetEnumerator()),调用MoveNext()移动枚举器,Current属性获取当前元素,遍历结束自动调用Dispose()释放资源。
  • 注:这个枚举器对象不是集合类对象,而是一个独立的类对象。

21.C#编译过程中的文件与概念

1.Unity中C#生命过程

  • 编写:C#源代码
  • 编译:编译器将源代码编译为IL与元数据,并打包成程序集文件
  • 分发/部署:程序集文件被发布
  • 执行:IL2CPP工具将IL与元数据转换为C++源代码,使用编译器将C++源代码编译成本地机器码,链接成本地可执行文件,在目标设备上直接执行本地机器码。

22.小知识点总结

1)A实例化赋给B,将B删除,A是否存在?

答:存在,B只是复制A栈中的地址,A的实例在堆中没有改变。

2)Foreach循环迭代时,若把其中的某个元素删除,程序报错,怎么处理?

答:由于foreach不能删除元素,因此需要记录找到索引或key值,迭代结束后再进行删除。

3)函数中多次使用string的+=处理,会产生大量内存垃圾,有什么好的方法可以解决?

答:使用StringBuilder高效拼接字符串,不会产生临时对象。

cs 复制代码
StringBuilder sb = new StringBuilder(100000); // 预估容量,减少扩容

for (int i = 1; i <= 10000; i++)
{
    sb.Append(i).Append(", ");
}

4)当需要频繁创建使用某个对象时,有什么好的程序设计方案来节省内存?

答:单例模式或者对象池。

5)单例模式违反了什么设计原则?

答:违反了依赖倒置原则。

相关推荐
aini_lovee2 小时前
C# 实现邮件发送源码(支持附件)
开发语言·javascript·c#
jessecyj2 小时前
SpringBoot详解
java·spring boot·后端
Flittly2 小时前
【SpringAIAlibaba新手村系列】(2)Ollama 本地大模型调用
java·ai·springboot
_MyFavorite_2 小时前
JAVA重点基础、进阶知识及易错点总结(10)Map 接口(HashMap、LinkedHashMap、TreeMap)
java·开发语言
qqty12172 小时前
Spring Boot管理用户数据
java·spring boot·后端
Flittly2 小时前
【SpringAIAlibaba新手村系列】(1)初识 Spring AI Alibaba 框架
java·spring
charlie1145141912 小时前
通用GUI编程技术——Win32 原生编程实战(十六)——Visual Studio 资源编辑器使用指南
开发语言·c++·ide·学习·gui·visual studio·win32
LSL666_3 小时前
MybatisPlus条件构造器(上)
java·数据库·mysql·mybatisplus
U-52184F693 小时前
深入理解“隐式共享”与“写时复制”:从性能魔法到内存深坑
java·数据库·算法