c#陌生点补充(泛型、深浅拷贝、存储在栈还是堆问题)

值类型和引用类型

由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 编译后的 MainDoWork 等方法的机器码
托管堆(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(公共语言运行时)想象成一个全能的项目执行引擎,它为你处理了从代码编译到运行管理的几乎所有底层工作。

  1. 通用中间语言(MSIL)与JIT编译 :你用C#、VB.NET等语言写的代码,会先被编译成一种标准的中间语言(MSIL或CIL) 。运行程序时,CLR的即时编译器(JIT) ​ 会按需将这些中间语言指令快速"翻译"成当前CPU能直接执行的本地机器码。这种方式实现了"一次编写,到处编译",同时JIT编译器还能根据程序运行的具体硬件进行优化,提升效率。

  2. 托管堆内存分配 :当使用new关键字创建引用类型对象(如类的实例)时,CLR会在托管堆 上为其分配内存。CLR通过维护一个名为NextObjPtr的指针来管理分配位置,新对象会紧挨着上一个对象存放,这种连续分配机制速度很快。

  3. 确保类型安全: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" → 完全独立!
相关推荐
ZHOUPUYU3 小时前
PHP 8.3网关优化:我用JIT将QPS提升300%的真实踩坑录
开发语言·php
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
l1t7 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
青云计划8 小时前
知光项目知文发布模块
java·后端·spring·mybatis
赶路人儿8 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar1238 小时前
C++使用format
开发语言·c++·算法
探路者继续奋斗8 小时前
IDD意图驱动开发之意图规格说明书
java·规格说明书·开发规范·意图驱动开发·idd
码说AI9 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS9 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
星空下的月光影子9 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言