我遍历一个 struct 列表挨个改字段,代码跑完一看列表里的值竟然一个都没变,我对着 C# 值类型处处是拷贝这个坑排查了大半天的复盘

写一段很普通的代码:有一个 List<某struct>,我用 foreach(或 list

我遍历一个 struct 列表去改它们的字段,改完发现一个都没变、修改全丢了,我对着 C# 值类型的拷贝语义排查了大半天的复盘

那是我用 C# 写的一段数据处理。我定义了一个 struct(结构体)Point,有一批 Point 放在 List 里。我用 foreach 遍历这个列表,在循环里修改每个 Point 的字段。代码读起来天经地义。可跑完一看,诡异极了:列表里的 Point,一个都没变!我在 foreach 里明明给每个 p.X 赋了新值,可循环结束后,列表里的值还是原来的。我对着代码反复看,赋值语句没问题啊。我甚至换了几种写法,有的直接编译报错(说不能修改),有的能编译但改了不生效。排查了大半天,我才真正理解了 C# 里 struct(值类型)和 class(引用类型)那个根本性的区别:值类型的拷贝语义。这篇就把这场"改了 struct 却不生效"的事故,从头复盘一遍。

故障现场:foreach 里改了 struct,列表却没变

先看现场。问题就藏在"struct 是值类型、处处是拷贝"上:

struct Point   // ✗ 用了 struct(值类型)
{
    public int X;
    public int Y;
}

var points = new List { new Point { X = 1 }, new Point { X = 2 } };

// 坑1: foreach 里改 struct 字段 —— 改了不生效(甚至编译报错)
foreach (var p in points)
{
    p.X = 100;   // ✗ 编译报错: 无法修改foreach迭代变量(它是只读副本)
                 // (即使能改, 改的也是副本, 列表里的不变)
}

// 坑2: 用索引取出来改 —— 也不生效!
for (int i = 0; i < points.Count; i++)
{
    var p = points[i];   // ★ points[i] 返回的是 struct 的【拷贝】!
    p.X = 100;           // 改的是拷贝 p, 不是列表里的那个
}
// 循环后: points[0].X 还是 1! (改的是拷贝, 列表里的没动)

// 为什么? struct 是"值类型", 赋值/传参/索引访问都是【拷贝】:
// 1. struct(值类型) vs class(引用类型):
//    - class: 变量存的是"引用(地址)", 赋值/传参复制引用 → 指向同一对象。
//    - struct: 变量存的是"值本身", 赋值/传参【复制整个值】→ 是独立的副本!
// 2. foreach 里的 p: 是列表元素的一个【拷贝】(且foreach变量是只读的)。
//    → 改 p 不影响列表里的原值。
// 3. points[i] 取出: 也是返回元素的【拷贝】(List索引器返回值类型是拷贝)。
//    → var p = points[i]; p.X=100; 改的是拷贝 p, 列表里的没变。
// 4. ★ 即: 对值类型(struct), 你"拿到"的几乎总是一个副本, 改副本不影响原值。

// 现象拼图:
//   - struct 是值类型, 赋值/传参/索引访问/foreach 都是"拷贝出一个副本"。
//   - 我以为我改的是"列表里的那个struct", 实际改的是"拷贝出来的副本"。
//   - 副本改了, 列表里的原值纹丝不动 → 修改全丢。
//   - ★ 根因: 我用了 struct(值类型), 却按 class(引用类型)的直觉去改它,
//     不知道值类型处处是拷贝, 改的全是副本。

看清真相后,我恍然大悟。问题的根源,是 struct 是"值类型",而值类型的赋值/传参/索引访问/foreach 都是"拷贝"。struct(值类型)vs class(引用类型)的根本区别:class 变量存的是"引用(地址)",赋值/传参复制引用、指向同一对象;而 struct 变量存的是"值本身",赋值/传参复制整个值、是独立的副本所以:foreach 里的 p 是列表元素的拷贝(且 foreach 变量只读),改 p 不影响列表;points[i] 取出也是返回元素的拷贝,var p = points[i]; p.X=100 改的是拷贝 p、列表里的没变即:对值类型,你"拿到"的几乎总是一个副本,改副本不影响原值根因是:我用了 struct(值类型),却按 class(引用类型)的直觉去改它,不知道值类型处处是拷贝、改的全是副本

第一件事:搞懂值类型和引用类型的根本区别

要解决它,得先彻底搞懂 C# 的值类型和引用类型。

值类型(struct) vs 引用类型(class)

# 一、最根本的区别: 变量里存什么?
#   - 值类型(struct, 以及int/double/bool/枚举等): 变量直接存"值本身"。
#   - 引用类型(class, 以及string/数组/集合等): 变量存"指向对象的引用(地址)"。

# 二、由此带来的行为差异: 赋值/传参/返回时
#   - 值类型: 复制【整个值】→ 得到一个独立的副本。
#     Point a = b;  → a 是 b 的完整拷贝, 改 a 不影响 b。
#     传给方法: 也是拷贝, 方法内改它不影响外面(除非用 ref)。
#   - 引用类型: 复制【引用(地址)】→ 两个变量指向同一对象。
#     MyClass a = b;  → a 和 b 指向同一对象, 改 a 就是改 b。

# 三、所以"改值类型"的坑:
#   - 凡是"拿到一个值类型"的地方(赋值、索引器返回、foreach、传参),
#     拿到的几乎都是【副本】, 改它不影响"原来的那个"。
#   - List[i] 返回拷贝; foreach 给拷贝; 方法参数是拷贝 → 改了都不生效。

# 四、什么时候用 struct, 什么时候用 class?
#   - struct(值类型): 适合"小的、不可变的、表示一个值"的数据
#     (如坐标Point、颜色Color、金额Money)。它无堆分配(在栈上/内联), 性能好。
#   - class(引用类型): 适合"有身份的、可变的、较大的"对象(大多数业务对象)。
#   - ★ 经验: 默认用 class; 只在"小、不可变、值语义、性能敏感"时才用 struct。
#   - ★ 尤其: 可变的 struct(mutable struct)是公认的坑源(本文), 强烈建议
#     struct 就设计成"不可变(只读)"的, 避免"改副本"的困惑。

# 核心: 值类型(struct)变量存值本身、赋值/传参/索引/foreach都复制整个值得到独立副本,
#   改副本不影响原值; 引用类型(class)存引用、共享同一对象; 默认用class, struct要设计成不可变。

想透值类型和引用类型,这个坑就清楚了。一、最根本的区别:变量里存什么?——值类型(struct、int/double/bool/枚举)变量直接存"值本身";引用类型(class、string/数组/集合)变量存"指向对象的引用"二、由此带来的行为差异:值类型赋值/传参复制整个值、得到独立副本(改 a 不影响 b);引用类型复制引用、两个变量指向同一对象(改 a 就是改 b)三、"改值类型"的坑:凡是"拿到一个值类型"的地方(赋值、索引器返回、foreach、传参),拿到的几乎都是副本,改它不影响原来的那个四、什么时候用 struct/class?——struct 适合"小的、不可变的、表示一个值"的数据(坐标、颜色、金额),无堆分配性能好;class 适合"有身份的、可变的、较大的"对象;经验是默认用 class,只在"小、不可变、值语义、性能敏感"时用 struct;尤其可变的 struct 是公认的坑源(本文),强烈建议 struct 设计成不可变的

第二件事:正解——改用 class、或重新赋值回去、或用不可变 struct

搞懂了原理,正解就清晰了:需要可变就用 class、用 struct 就重新赋值回列表、或把 struct 设计成不可变(用 record/with)

// ====== 正解一(最常见): 需要"可变、引用语义"就用 class ======
class Point   // ✓ 改成 class(引用类型)
{
    public int X;
    public int Y;
}
var points = new List { new Point { X = 1 }, new Point { X = 2 } };
foreach (var p in points)
{
    p.X = 100;   // ✓ p 是引用, 指向列表里的对象, 改它就是改列表里的!
}
// 循环后: points[0].X == 100 ✓
// → 大多数"需要在集合里修改"的场景, 用 class 最直观(引用语义, 改了就生效)。

// ====== 正解二: 坚持用 struct, 就"改完重新赋值回去" ======
struct PointS { public int X; public int Y; }
var list = new List { new PointS { X = 1 } };
for (int i = 0; i < list.Count; i++)
{
    var p = list[i];   // 取出拷贝
    p.X = 100;         // 改拷贝
    list[i] = p;       // ★ 把改后的拷贝【写回】列表! (覆盖原来的)
}
// list[0].X == 100 ✓ (通过"取出-改-写回"实现修改)

// ====== 正解三(推荐, struct就该这样): 不可变 struct + 创建新实例 ======
readonly struct PointR   // ★ readonly struct: 不可变
{
    public int X { get; }
    public int Y { get; }
    public PointR(int x, int y) { X = x; Y = y; }
    public PointR WithX(int x) => new PointR(x, Y);  // "改"= 创建新实例
}
// 用: list[i] = list[i].WithX(100);  // 不可变, "修改"就是创建新的赋回去
// → 不可变struct避免了"改副本"的困惑(它根本不能原地改, 只能创建新的)。

// ====== 正解四(C# 9+ record struct): 简洁的不可变值类型 ======
public readonly record struct Point2(int X, int Y);
// 用 with 表达式"修改"(创建新实例):
// list[i] = list[i] with { X = 100 };   // ✓ 创建一个X改了的新Point2
// → record struct + with, 是现代C#里"不可变值类型"最简洁的写法。

// ====== 正解五: 传 struct 给方法要改它, 用 ref ======
void Move(ref PointS p) { p.X += 10; }   // ref: 传引用而非拷贝
// Move(ref myPoint);  // ✓ 这样方法内的修改能影响外面
// (但: 一般更推荐"不可变 + 返回新值", 少用 ref 改值类型)

// 核心: 要可变/引用语义就用class(改了就生效); 坚持struct就"取出-改-写回list[i]=p";
//   推荐把struct设计成不可变(readonly struct/record struct + with), 避免"改副本"困惑。

修复的核心,是"要么用引用语义的 class,要么正确处理 struct 的值语义(写回/不可变)"正解一(最常见):需要"可变、引用语义"就用 class——class 是引用类型,foreach 里的 p 是引用、指向列表里的对象,改它就是改列表里的;大多数"需要在集合里修改"的场景用 class 最直观正解二:坚持用 struct 就"改完重新赋值回去"——"取出拷贝 → 改拷贝 → list[i] = p 写回",通过"取出-改-写回"实现修改正解三(推荐,struct 就该这样):不可变 struct + 创建新实例——readonly struct 不可变,"改"就是创建新实例;不可变 struct 避免了"改副本"的困惑(它根本不能原地改)正解四(C# 9+ record struct):简洁的不可变值类型——readonly record struct + with 表达式"修改"(创建新实例),是现代 C# 里不可变值类型最简洁的写法正解五:传 struct 给方法要改它用 ref(但更推荐不可变 + 返回新值)。归根结底:要可变/引用语义用 class(改了就生效);坚持 struct 就"取出-改-写回";推荐把 struct 设计成不可变(readonly/record struct + with),避免"改副本"困惑。

第三件事:struct(可变值类型)的其他常见坑

排查后我把可变 struct 的其他常见坑也系统梳理了一遍,它们一样隐蔽。

可变 struct 的其他常见坑

# 1. foreach/索引器取出改不生效(本文): 拿到的是拷贝。→ 写回/用class。

# 2. struct 作为属性, 改它的字段:
#    obj.Position.X = 5;  // ✗ 可能编译报错或不生效!
#    (obj.Position 返回的是拷贝, 改拷贝的X无意义)
#    → 改成: var pos = obj.Position; pos.X = 5; obj.Position = pos;

# 3. struct 放进 readonly 字段, 调用会改它的方法:
#    readonly Point p;  p.Mutate();  // 编译器会"防御性拷贝"再调用!
#    → readonly的值类型, 调用其方法时会先拷贝, 改的是拷贝。

# 4. 大 struct 频繁拷贝, 性能反而差:
#    - struct 赋值/传参都是拷贝整个值。如果struct很大、又频繁传递,
#      大量拷贝反而比class(只拷贝引用)慢、占更多栈。
#    → struct 要"小"(一般建议 ≤ 16字节/几个字段); 大的用 class。

# 5. struct 实现接口时装箱:
#    - struct 赋给接口变量时会"装箱"(boxing, 拷贝到堆), 有性能开销。

# 6. 默认值: struct 不能有无参构造设默认值(C#10前), 字段都是零值。
#    - new Point() 或 default(Point): 所有字段是0/null/false。

# 共同根源: struct 是值类型, 它的"值语义、处处拷贝"和我们对"对象"的
#   引用语义直觉相悖; 可变struct尤其容易因"改的是拷贝"而出错。

# 核心: 可变struct坑多(foreach/属性/readonly改不生效、大struct拷贝慢、接口装箱); 根源是
#   值语义处处拷贝、与引用直觉相悖; 强烈建议struct设计成不可变, 或干脆用class。

排查让我把可变 struct 的其他坑也梳理清了。一、foreach/索引器取出改不生效(本文)。二、struct 作为属性改它的字段——obj.Position.X = 5 可能编译报错或不生效(obj.Position 返回拷贝),要"取出-改-写回"。三、struct 放进 readonly 字段、调用会改它的方法——编译器会"防御性拷贝"再调用,改的是拷贝。四、大 struct 频繁拷贝性能反而差——struct 赋值/传参拷贝整个值,大 struct 频繁传递反而比 class 慢;struct 要小(≤16 字节)。五、struct 实现接口时装箱(有开销)。六、默认值都是零值它们的共同根源是:struct 是值类型,它的"值语义、处处拷贝"和我们对"对象"的引用语义直觉相悖;可变 struct 尤其容易因"改的是拷贝"而出错核心是:强烈建议 struct 设计成不可变,或干脆用 class下面这张图,是这次改 struct 不生效的成因与解法:

第四件事:值类型 vs 引用类型行为对比速查

这次踩坑后,我把值类型和引用类型的行为差异整理成一张表,用 struct/class 时对照着想。

操作 值类型 struct 引用类型 class
变量存什么 值本身 引用(地址)
赋值 a = b 拷贝整个值,独立 复制引用,指向同一对象
传给方法 拷贝(改不影响外面) 引用(改影响外面)
从 List 取出改 改拷贝,不生效(本文) 改的是原对象,生效
foreach 里改 编译错/改拷贝 改的是原对象,生效
== 比较 比值(逐字段) 默认比引用(同一对象)
分配位置 栈/内联(无堆分配) 堆(有GC)

这张表,把值类型和引用类型的行为差异钉死了。核心区别就一条:值类型是"拷贝"语义(处处独立副本),引用类型是"共享"语义(指向同一对象);这一条差异,贯穿了赋值、传参、取出、比较的所有行为。它给我的最大启发是:"值语义"和"引用语义",是两种根本不同的"数据如何被传递和共享"的模型;而很多 bug,都源于"用一种语义的直觉,去操作另一种语义的类型"(我用引用语义的直觉去改值类型的 struct,所以改了不生效)。这其实是一个跨语言的普遍主题:几乎每种语言都有"值 vs 引用"(或类似)的区分——C# 的 struct/class、Java 的基本类型/对象、Go 的值/指针、C++ 的值/引用、JS 的原始类型/对象……;它们的具体规则各异,但核心都是在回答"这个数据,赋值/传递时,是复制一份还是共享同一份?"所以,每学一门语言、用一个类型,都要搞清楚它的"值语义还是引用语义";因为这个语义,直接决定了"你改它,会不会影响到别处"——而这,是写出正确代码的基础认知之一

第五件事:可变性带来的复杂度

这次的坑,深层原因是 struct 的"可变性"。我把可变与不可变的对比再次梳理了一下(这次从值类型角度)。

维度 可变 struct 不可变 struct(readonly/record)
改副本不生效的坑 ✗ 易踩(本文) ✓ 避开(根本不能原地改)
防御性拷贝 readonly字段调方法会拷贝 无此问题
线程安全 差(可变共享) ✓ 天然安全
可预测性 差(可能被意外改) ✓ 创建后不变
"修改"方式 直接改字段(各种坑) 创建新实例(with)
推荐度 不推荐(公认坑源) ✓ 推荐

这张表,再次印证了"不可变"的价值——这次是从值类型的角度。核心是:可变 struct 是公认的坑源(本文就是一例),而不可变 struct(readonly struct/record struct)避开了所有这些坑(根本不能原地改,只能创建新的)它给我的启发,和我在别的语言里(JS 浅拷贝、Java 集合)反复体会到的一致:"可变性(mutability)",尤其是"共享的可变状态",是大量隐蔽 bug 的温床;而"不可变(immutability)",虽然"修改时要创建新对象"看似麻烦,却从根上消除了一大类问题(意外修改、改副本不生效、并发竞态、状态难追踪)这让我越来越坚定一个跨语言的设计倾向:默认优先用"不可变"——把数据设计成"创建后就不再改变",需要"变化"时创建新的数据;无论是 C# 的 record、JS 的不可变更新、Java 的 record、还是函数式编程的核心理念,整个行业都在越来越拥抱"不可变"具体到 struct,这意味着:如果你要用 struct(值类型),就把它设计成不可变的(readonly record struct)——这样既享受值类型的性能,又避开可变 struct 的所有坑拥抱不可变,是写出更可靠、更可预测代码的一条朴素而强大的原则。

第六件事:用 struct/class 时,我现在的判断习惯

现在每当我要定义一个类型、或操作一个值类型,我都会按这张图先想清楚:

这张图的精髓,是"定义类型先想值语义还是引用语义,操作值类型时记住处处是拷贝"定义时:有身份/可变/较大的用 class;一个小的、不可变的值才用 struct,且设计成不可变(readonly record struct)操作已有的值类型时:记住取出/foreach/传参拿到的都是拷贝;要改并生效就写回 list[i]=p、或用 class、或不可变创建新的最后改完验证原值真的变了吗这套习惯,让我用 struct/class 时,从"按引用直觉随便改"变成了"先想清楚值/引用语义、操作值类型时警觉拷贝"——核心始终是:struct 是值类型处处是拷贝改副本不生效,默认用 class、struct 要设计成不可变。

我立下的几条规矩

这场"改 struct 不生效"的事故,换来了我写 C# 时,刻进骨子里的几条铁律:

  1. struct 是值类型,处处是拷贝。赋值/传参/索引/foreach 拿到的都是独立副本。
  2. 改 struct 副本不影响原值。foreach/list[i] 取出来改,列表里的纹丝不动。
  3. 默认用 class。只在"小、不可变、值语义、性能敏感"时才用 struct。
  4. 可变 struct 是公认坑源。用 struct 就设计成不可变(readonly record struct)。
  5. 坚持用可变 struct 改它,要"取出-改-写回"。list[i] = p 把改后的写回。
  6. 分清值语义和引用语义。它决定"改它会不会影响别处",是写对代码的基础。
  7. 不确定改没改成,验证一下。对值类型尤其要确认"原值真的变了"。

附:一段亲眼看清 struct vs class 行为差异的实验

口说无凭。下面用一段对比代码,让你亲眼看到 struct(值)和 class(引用)在"改"时的不同表现:

using System;
using System.Collections.Generic;

class StructVsClassDemo
{
    struct PointStruct { public int X; }   // 值类型
    class PointClass { public int X; }     // 引用类型

    static void main()
    {
        // ====== 1. 赋值: struct是拷贝, class是共享 ======
        var s1 = new PointStruct { X = 1 };
        var s2 = s1;            // 拷贝
        s2.X = 100;
        Console.WriteLine($"struct 赋值: s1.X={s1.X}, s2.X={s2.X}");
        //   s1.X=1, s2.X=100  ← s1不受影响(独立副本)

        var c1 = new PointClass { X = 1 };
        var c2 = c1;            // 共享引用
        c2.X = 100;
        Console.WriteLine($"class 赋值:  c1.X={c1.X}, c2.X={c2.X}");
        //   c1.X=100, c2.X=100  ← c1也变了(同一对象)

        // ====== 2. 从List取出改: struct不生效, class生效 ======
        var listS = new List { new PointStruct { X = 1 } };
        var ps = listS[0];     // 拷贝
        ps.X = 100;            // 改拷贝
        Console.WriteLine($"struct 从List改: listS[0].X={listS[0].X}");
        //   listS[0].X=1  ← 列表里的没变(改的是拷贝)!

        var listC = new List { new PointClass { X = 1 } };
        var pc = listC[0];     // 引用
        pc.X = 100;            // 改的是列表里的对象
        Console.WriteLine($"class 从List改:  listC[0].X={listC[0].X}");
        //   listC[0].X=100  ← 列表里的变了 ✓

        // ====== 3. struct 正确改法: 写回 ======
        var p = listS[0];
        p.X = 100;
        listS[0] = p;          // ★ 写回
        Console.WriteLine($"struct 写回后: listS[0].X={listS[0].X}");
        //   listS[0].X=100  ← 写回后才生效 ✓

        // ====== 4. 传给方法: struct不影响外面, class影响 ======
        Modify(ref ps);        // struct要改外面得用ref
    }
    static void Modify(ref PointStruct p) { p.X = 999; }
}

/* 输出(对比鲜明):
   struct 赋值: s1.X=1, s2.X=100        ← struct独立(改拷贝)
   class 赋值:  c1.X=100, c2.X=100      ← class共享(改同一对象)
   struct 从List改: listS[0].X=1        ← struct改不动(本文的坑!)
   class 从List改:  listC[0].X=100      ← class改得动
   struct 写回后: listS[0].X=100        ← struct写回才生效
*/

// 核心: 同样的"改",struct(值)改的是独立拷贝(原值不变)、class(引用)改的是共享对象(原值变);
//   struct从List改要写回才生效。跑一遍, 值/引用语义的差别一目了然。

这段对比代码,把"struct 和 class 在'改'时的不同"变成了可以亲眼对比的输出。它用同样的"赋值后改""从 List 取出改""传给方法改"操作,分别试了 struct 和 class:你会清清楚楚地看到,struct 赋值后改副本原值不变(s1.X 还是 1)、从 List 取出改列表里的纹丝不动(listS[0].X 还是 1,正是本文的坑)、写回后才生效;而 class 因为是共享引用,改了到处都变(c1.X、listC[0].X 都变 100)这一组对比,把抽象的"值语义 vs 引用语义",变成了具体的、肉眼可辨的数字差异。这,正是我想用这段代码,留给每个 C# 开发者的最后一课:"值类型 vs 引用类型"是 C#(乃至很多语言)一个最基础、却最容易因直觉而搞错的概念;而搞清它最好的方式,就是写一段对比代码,让 struct 和 class 在同样的操作下,把它们行为的差异''给你看当你亲眼看到 listS[0].X 那个固执的 1(struct 改不动)、和 listC[0].X 那个听话的 100(class 改得动)的对比,"值类型是拷贝、引用类型是共享"这个概念,就会以一种具体而牢固的方式刻进你的认知。把抽象的语义差异,变成具体的、可对比的运行现象——这,是我这一整个系列复盘里贯穿始终、屡试不爽的、理解一切"抽象概念"和"反直觉行为"的核心方法对任何拿不准的语义、行为、规则,别在脑子里空想,写个对比小实验,让代码自己把答案演给你看——这份"动手验证"的习惯,胜过读一百遍文档。

写在最后

回头看,这场由"struct 值类型拷贝"引发的、改了不生效的事故,真正教给我的,远不止"用 class 或写回"这一个技巧。它让我对"同一个动作,在不同语义下含义不同"有了又一次深刻的体会。我栽跟头,是因为"修改一个对象的字段"这个动作,在我熟悉的"引用类型(class/对象)"世界里,含义是"改那个唯一的、共享的对象"(改了到处都生效);可在"值类型(struct)"的世界里,同样一个动作,含义却变成了"改我手里这个独立的副本"(改了别处不受影响)。我用引用世界的直觉("改字段就是改那个对象"),去操作值世界的类型,自然就出了"改了不生效"的偏差。这让我领悟到一个深刻的认知:同样的代码、同样的操作,其"真实含义和效果",会因为操作对象的"底层语义(值 vs 引用)"不同而截然不同;我们写代码时,不能只看"语法上做了什么操作"(p.X = 100),还要看"这个操作作用在什么语义的类型上、实际会产生什么效果"这其实是一个普遍的道理:"形式相同的操作",在"不同性质的对象/上下文"下,可以有"不同的语义和后果";真正理解代码,不能停留在"它写了什么",而要深入到"它在这个具体的语义环境下,实际做了什么"透过操作的"形式"看它在具体语义下的"实质"——这,是我用一次"struct 改不动"的事故,换来的、关于 C#、也关于"值语义与引用语义"的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次用 struct 时,先想想"它是值类型、我拿到的是拷贝",那我对着那个怎么改都不变的 struct 列表熬的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我给接口加了"每分钟最多 100 次"的限流,结果它还是在某个瞬间被 200 次请求打穿了,我对着固定窗口限流的临界问题排查了大半天的复盘

2026-6-2 9:18:05

技术教程

我用 as 把后端返回的 JSON 断言成 User 类型,TypeScript 编译一路绿灯,结果上线访问字段直接运行时崩溃白屏,我对着类型断言只骗编译器不做运行时检查排查大半天的复盘

2026-6-2 9:30:46

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索