值类型和引用类型
由object(引用类型)派生而来的两个范围,值类型和引用类型
值类型
简单类型(int,double)、结构体、枚举
特点:适合做数据的载体
引用类型
类、接口、string、数组
特点:可以派生支持多态
值类型和引用类型在内存中的位置
核心原则 :值类型存储在它被声明的地方,而引用类型的实例(对象本身)总是存储在堆上
-
声明的地方"是关键
这是理解内存分配的核心。一个
int类型的变量,如果它在方法内部声明,就是局部变量,存在于栈上。但如果这个int是某个类的一个字段(例如class Person { public int Age; }),那么当创建Person实例时(new Person()),这个Age字段就会作为Person对象的一部分,存在于堆上。 -
引用类型的变量和实例是两回事
这是一个非常重要的区分。对于代码
MyClass obj = new MyClass();:-
obj这个变量本身是一个引用(可以理解为一个地址指针),它存储在线程栈上。 -
new MyClass()创建的对象实例则存储在托管堆上。 -
=操作的作用是将堆中对象的内存地址赋值给栈上的obj变量
-
两种引用方式方便debug
项目引用:选中dll项目的project文件
dll引用:选中依赖的项目的后缀为dll的文件
加载dll的方式
**加载策略:**将添加的dll及其他的依赖,复制到bin中
若dll本身还有依赖项,那么依赖项(托管代码)和dll最后会被复制到bin目录,若非托管代码,要将其复制到dll下
Object
是所有Class的基类
一、集合
1.1.数组
cs
//定义
string[] devices = new string[3];
//增
devices[0] = "电机A";
devices[1] = "传感器B";
//删
无法真正删除只能是跳过这个元素赋值给新数组
//改
devices[0]="电机B";
1.2.List
注意改的时候任然使用索引器,但是List虽然是动态列表但是只能使用Add函数来增加位数,不能直接devices[100]=''hello''这样会超出数组索引范围
cs
//定义
var devices = new List<string> { "电机A", "传感器B", "PLC-C" };
//增
devices.Add("变频器D");
//改
devices[3] = "PLC-C升级版";
//按照值删除
devices.Remove("传感器B");
//按照索引删除
devices.RemoveAt(0);
//查
string s = devices[0];
1.3.observableCollection(可观察集合)
**特点:**只要修改observableCollection的元素就会通知ui变化,无需手动通知
注意:当元素内部变化,例如元素为对象,对象的属性值发生就不会通知
cs
//定义
var devices = new ObservableCollection<string> { "电机A", "传感器B", "PLC-C" };
//增
devices.Add("变频器D");
devices.Insert(0, "编码器E");
Console.WriteLine("--增---");
//删
Console.WriteLine("--删---");
devices.Remove("传感器B");
devices.RemoveAt(2);
//改
devices[0] = "1111";
//查
string st = devices[0];
//清空
devices.Clear()(通知 UI)
泛型
作用:实现同一份代码上操作多种数据类型
泛型可以修饰:类、方法、委托
语法:在修饰对象名称后加上占位符
具体类型在实例化的时候才特化
泛型可以提供多种类型的占位符

泛型类
在实例化的地方特化
cs
static void Main(string[] args)
{
var obj=new genericClass<int>();
obj.add(0, 10);
Console.WriteLine(obj.arr[0]);
}
}
class genericClass<T>
{
public T []arr= new T[10];
public void add(int index, T value)
{
arr[index] = value;
}
}
泛型类的两种继承
cs
//特化继承
class Son: genericClass<int>
{
}
//泛型继承
class Son2<T> : genericClass<T>
{
}
泛型方法
调用的时候做特化
实例一:无返回值
cs
static void Main(string[] args)
{
int a, b;
Exchange<int>( a= 10, b= 20);
}
private static void Exchange<T>( T a, T b)
{
T temp = a;
a = b;
b = temp;
Console.WriteLine($"{a},{b}");
}
实例二:有返回值
插入知识点:dynamic将类型校验推迟到运行时
否则在编译阶段就会报错,因为不是所有类型都能相加
cs
static void Main(string[] args)
{
int result = add<int>(10, 20);
Console.WriteLine(result);
}
private static T add<T>(T a, T b)
{
dynamic x = a;
dynamic y = b;
return x+y;
}
泛型约束
对泛型传入的参数进行校验,规定其必须满足一定条件
泛型约束分类
基本格式为在泛型类型名称后加上where T:条件
class
class表示泛型必须是一个引用类型
cs
class stu<T> where T:class
{
}
static void Main(string[] args)
{
var a = new stu<string>();
}
struct
struct泛型必须是值类型
cs
//值类型
class stu1<T> where T : struct
{
}
new()
T必须包含无参数构造方法
cs
//T,必须包含无参数构造方法
class stu2<T>where T : new()
{ }
类名约束
约束是一个类型的时候,特化时必须是本类或者子孙类
cs
class GenericForClassName<T>where T : animal
{
}
static void Main(string[] args)
{
var Tom = new GenericForClassName<people>();
}
接口约束
特化的T必须继承某种接口
cs
interface Ifireable
{
void Fire();
}
interface IRunnable
{
void Run();
}
class Tank : IRunnable, Ifireable
{
public void Fire()
{
Console.WriteLine("开火");
}
public void Run()
{
Console.WriteLine("跑起来了");
}
}
class MyGenericClassForInterFace<T>where T : IRunnable, Ifireable
{
}
//接口约束
static void Main(string[] args)
{
var Tom = new MyGenericClassForInterFace<Tank>();
}
多条件
如果约束条件中有new()必须是最后一个条件
如果约束条件有接口约束和类名约束结合时,类名要放在前方
cs
class normal
{
}
//多个条件
class foo<T>where T : class,new() { }
static void Main(string[] args)
{
var aaa = new foo<normal>();
}
多占位符约束
cs
class MyGnerics<T, U>where T:class where U:struct
{
}
static void Main(string[] args)
{
var aaa = new MyGnerics<string, int>();
}
泛型约束继承
拥有泛型约束的类作为父类,子类可以选择特化父类,或者使用更严格/同样的约束
程序的一生
- 代码编写完成:存储在你的硬盘上
- 编译时:源代码编译为 IL(Intermediate Language) + 元数据 ,打包进 程序集(.dll 或 .exe)。
运行时开始分配内存
| 区域 | 存放内容 |
|---|---|
| 代码段(Code Heap / JIT Code Cache) | JIT 编译后的 Main、DoWork 等方法的机器码 |
| 托管堆(Managed Heap) | 所有 new 出来的对象、装箱值、字符串实例等 |
| 线程栈(Thread Stack) | 每个方法调用的栈帧(局部变量、参数等) |
| 元数据区(Loader Heap) | 类型信息(Type objects)、方法表等 |
栈帧
当你调用一个方法时,CLR(Common Language Runtime)会在 调用栈(Call Stack) 上为该方法分配一块内存区域,称为 栈帧(Stack Frame)。
栈帧内容
- 方法的局部变量(包括值类型和引用类型的引用)
- 参数
- 返回地址等控制信息
回收时机:方法执行完毕并返回时
当方法执行结束(正常返回或抛出异常后 unwinding),其对应的 整个栈帧会被立即弹出(pop) ,其中所有局部变量(无论值类型还是引用)所占的内存 瞬间释放。
例子

-
参数和局部变量 :栈帧上的数据(如参数
a,b和局部变量result)是直接存储在栈上的值或引用 。对于值类型参数,传递的是值的副本。这些数据并非从堆上的实例"拷贝"而来 。实例本身的数据(字段)位于堆上,当方法内的代码需要访问实例的字段时,会通过this指针(一个隐含的、指向堆上实例的引用)去堆上找到并操作它们。 -
执行过程:执行引擎通过实例的类型对象指针找到方法的代码(在方法区),然后将代码指令加载执行。计算过程中需要的操作数(如常数、局部变量、参数的值)则来自栈帧
clr和gc概念
可以把CLR(公共语言运行时)想象成一个全能的项目执行引擎,它为你处理了从代码编译到运行管理的几乎所有底层工作。
-
通用中间语言(MSIL)与JIT编译 :你用C#、VB.NET等语言写的代码,会先被编译成一种标准的中间语言(MSIL或CIL) 。运行程序时,CLR的即时编译器(JIT) 会按需将这些中间语言指令快速"翻译"成当前CPU能直接执行的本地机器码。这种方式实现了"一次编写,到处编译",同时JIT编译器还能根据程序运行的具体硬件进行优化,提升效率。
-
托管堆内存分配 :当使用
new关键字创建引用类型对象(如类的实例)时,CLR会在托管堆 上为其分配内存。CLR通过维护一个名为NextObjPtr的指针来管理分配位置,新对象会紧挨着上一个对象存放,这种连续分配机制速度很快。 -
确保类型安全:CLR会严格检查代码中的类型操作,确保不会发生将整数误当作字符串使用这类错误,从而增强程序的稳定性和安全性
内存垃圾回收机制(gc)
托管资源分配
需要的时候new出来,不需要的时候自动释放
非托管资源
只能跟踪其生存周期,而不能决定如何释放资源,如数据库链接,文件句柄和指针结果
资源并不全是内存:数据库链接和文件句柄都是越用越少的。
垃圾收集
从程序的根对象(root)开始层层遍历在堆上分配的对象,不再被引用的判定为垃圾,可被引用的称为Reachable Object;

全局对象和静态变量是长期存在的,栈上现存变量和cpu寄存器变量是改变的
深拷贝/浅拷贝/直接赋值(待补充)
区分深拷贝浅拷贝和直接赋值
- 浅拷贝(Shallow Copy) :只复制对象本身,不复制它内部引用的对象。
- 深拷贝(Deep Copy) :不仅复制对象本身,连它内部所有引用的对象也递归复制一份新的
赋值操作:"引用类型的赋值和传参,其效果等同于浅拷贝",因为值类型是复制,而引用类型赋值是存放原来的引用对象地址
函数传递参数是浅拷贝
string虽然在底层上是引用类型,但是在特性上是值类型。
| 问题 | 回答为"是" → 需要深拷贝 |
|---|---|
| 这个对象里有没有引用类型字段? | ✅ 有 → 可能需要深拷贝 |
| 我是否希望修改副本不影响原对象? | ✅ 是 → 必须深拷贝 |
定义一个类
public class Pen
{
public string Color { get; set; }
}
public class Backpack
{
public int NotebookPage { get; set; } // 值类型
public Pen MyPen { get; set; } // 引用类型
}
浅拷贝实例
cs
var original = new Backpack
{
NotebookPage = 10,
MyPen = new Pen { Color = "Blue" }
};
// 浅拷贝
//MemberwiseClone意为逐个函数成员克隆
var shallow = (Backpack)original.MemberwiseClone();
// 修改浅拷贝的值类型
shallow.NotebookPage = 20;
Console.WriteLine(original.NotebookPage); // 输出 10 → 没影响(值类型是独立的)
// 修改浅拷贝的引用类型
shallow.MyPen.Color = "Red";
Console.WriteLine(original.MyPen.Color); // 输出 Red!→ 共用同一个 Pen 对象!
深拷贝实例
cs
public class Backpack
{
public int NotebookPage { get; set; }
public Pen MyPen { get; set; }
// 手动深拷贝方法
public Backpack DeepCopy()
{
return new Backpack
{
NotebookPage = this.NotebookPage,
MyPen = new Pen { Color = this.MyPen.Color } // 创建新 Pen
};
}
}
var deep = original.DeepCopy();
deep.MyPen.Color = "Green";
Console.WriteLine(original.MyPen.Color); // 仍是 "Blue" → 完全独立!