尧图精选

C# this关键字深度解析:从五种用法到隐藏参数本质

🕒 发布时间:2026/9/9 5:36:54 📁 来源:尧图网络
1. 你以为的 this 与它真正的五种身份先说个我自己面试时经常遇到的场景。我问候选人“this 关键字是干什么的”十个人里有八个回答“指当前对象”然后我就接着问“那你给我说说扩展方法第一个参数前面为什么要加 this”能答上来的人立刻少了一半。这其实不能怪大家基础不牢C# 的 this 在教材里通常只作为“指代当前实例”的一种语法糖出现但往深处走几步就会发现它在构造函数链、扩展方法、索引器、ref 返回这些场景里各顶各的角色真实面目远比课本写的复杂。我刚工作那会儿写 Winform满脑子都是 this.Text、this.button1.Enabled 这种用法总觉得 this 就是“为了防止参数名和字段名撞车而存在的加前缀的工具”。后来开始做上位机、写一些偏底层的通讯封装才慢慢意识到 this 本质上是一个“隐藏参数”这个认知的转变让我对整个实例方法调用模型都通透了。如果你看这篇文章之前也是那种“大概会用但说不清原理”的状态那恭喜你看完之后你会对 C# 这套类型系统的设计思路有一个显著的提升。这篇文章适合谁不止是刚入门的 C# 学习者。如果你已经在用 C# 做上位机、业务系统、Unity 脚本其实也会从里面找到值得留意的点尤其是值类型中修改 this 的机制、反射调用时 this 作为参数的传递逻辑以及事件委托里 this 作为 sender 的本质——这些内容平时文档里写得分散面试时却是高频考点工作中理解透了能省不少排查问题的时间。1.1 从“指当前对象”到“五种身份”把 C# 的 this 简单理解为“当前对象”错不错不算错但只覆盖了它的一部分使用场景。我习惯把它拆成五类来看这个分类能帮助记忆也能帮助你在读到别人代码时更快抓准作者意图实例方法内引用当前实例成员obj.Method() 里方法体内用 this 能找到那个“被调用者的引用”。构造器初始化器this(...) 用于让一个构造函数在开始前先调用同类的另一个构造函数形成构造链。扩展方法的第一个参数标记表示这个静态方法会被“附加”到某个类型上以实例方法的写法调用。索引器声明this[int index] 这种属性语法里this 代表“当前对象的索引器”不是指成员访问。值类型中作为 ref 参数的载体在 struct 的方法内部this 会被编译成对当前变量的引用允许你对它本身做修改。这五类看着各不相干其实逻辑全部统一在“this 不是语法糖而是真实传递的参数”这条线下面。我们直接看第二层本质。2. 从调用约定看 this 的本质编译器如何“塞参数”很多 C# 开发者没有接触过 C对“类成员函数本质是一个带隐藏参数的函数”这种说法毫无概念。这很正常因为 C# 语法层面把 this 藏得太好了你写出来的每个实例方法都像天生就该知道自己是“谁的代码”但 CLR 并不会魔法般地把这些信息塞进方法里它靠的正是每次调用时悄悄多传的那个参数也就是 this。2.1 实例方法调用时发生了什么假设有一段最简单的代码class Device { public string Name { get; set; } public void ShowName() { Console.WriteLine(this.Name); } } var d new Device { Name 采集卡 }; d.ShowName();你看到的是一次属性访问和一次方法调用。但如果你尝试从 IL 层面理解实际上编译器生成的调用序列接近这样ldloc.0 // 加载 d 的引用 callvirt instance void Device::ShowName()这个过程中d 的引用被作为“第 0 个参数”压到了求值栈上然后进入 ShowName 方法内部。你在方法体里写的 this.Name本质上是读取第 0 个参数所指向对象的 Name 属性。换句话说每次调用 d.ShowName()CLR 做的事和调用一个全局方法 ShowName(d) 在参数传递上是没有本质区别的。这个认知有什么用最直接的作用是理解“为什么实例方法里可以放心使用 this而静态方法里不能”。静态方法没有这个隐藏参数所以你在静态方法里写 this 编译器直接报错。更深一层当你做反射时这种“this 是参数”的理解能帮你完整地解释 Invoke 方法的签名——你调用一个实例方法时为什么第一个参数要传对象实例MethodInfo showName typeof(Device).GetMethod(ShowName); showName.Invoke(d, null); // 第一个参数 d 就是 this如果被调方法是有参数的比如 Something(int x)则 Invoke 的第二个参数数组里要按顺序放参数这也反过来印证了“实例参数和方法参数一起构成完整调用参数列表”这一结论。2.2 this 在值类型与引用类型中的差异引用类型里this 当然是一个引用指向堆上对象。值类型中最典型的是 struct你也许已经见过类似写法struct Point { public int X; public int Y; public void Move(int dx, int dy) { X dx; Y dy; } }Point 是值类型当它作为局部变量存在时它的字段存在栈或寄存器上。Move 方法内写 X dx 时这个 X 到底改的是谁如果把 this 仅仅理解成“当前对象”这里很容易产生迷惑。实际上struct 中 this 的本体是“当前变量本身”编译器以传引用方式传入所以方法内对字段的修改会真实作用于调用方那个变量上而非一份副本var p new Point(1, 2); p.Move(3, 4); Console.WriteLine(${p.X}, {p.Y}); // 输出 4, 6这一点初学者通常会踩坑。假如你把上例中 Point 的字段改成属性然后在 Move 方法里写 X dx仍然能编译但如果你在方法里写 this new Point(...)则普通方法里是会报错的只有构造函数内部才允许对 this 做这种赋值式的初始化。实际工作中做工业控制时我常用这种方式封装小结构体用来表示机械轴的坐标并暴露移动方法C# 对 struct 方法内 this 的按引用传递保证了方法调用后局部变量真的被修改这在性能与可读性上很有价值。2.3 this 为 ref 变量带来的读改写能力上面说编译器会把 this 以按引用方式传入 struct 方法这句话完整的翻译是对于值类型实例方法的调用this 会被编译成托管引用等价于 ref 参数。如果你还不信来看一个更“野”的写法。值类型方法内部可以给 this 赋值吗C# 从 7.2 开始在结构体方法内可以直接对 this 赋值要求该结构体没有只读限制前提是不在 readonly 修饰的方法里struct Counter { public int Value; public void Reset() { this new Counter(); // 合法相当于把整个结构体变量重置 // 实际等价于 Value default; } }这个写法看着奇怪却能帮我们验证一件事this 不是“对象的别名”而是“当前变量的别名”。一个结构体变量被 Reset 后不仅 Value 归零整个变量的所有状态都回到默认值行为与直接对局部变量赋值 new Counter() 完全一致。这种机制对编写无 GC 压力的紧凑数据模型有大用也是 Unity DOTS 领域比较常见的 C# 技巧。理解“this 是隐藏参数”之后很多高级用法就不再是死记硬背的东西了。接下来我们把五种典型场景逐个过一遍每一处都标注了背后的“为什么”方便各位以后写码时不光会用还能给同事讲明白原理。3. this 在工程中的高频用法逐个拆解3.1 构造器链this(...) 如何复用初始化逻辑在 C# 里构造函数与构造函数之间可以通过 this 互相调用。先看例子class SerialPortClient { private readonly string portName; private readonly int baudRate; public SerialPortClient(string portName) : this(portName, 9600) { } public SerialPortClient(string portName, int baudRate) { this.portName portName; this.baudRate baudRate; } }第一个构造函数签名后跟了 : this(portName, 9600)它的意思是“在执行本构造函数体之前先执行同类的另一个构造函数传入 portName 和 9600”。这样的好处清楚明了默认波特率、校验位、停止位的组合只需要在一处写初始化逻辑其他形参较少的构造函数都往那头“带参跳转”避免复制粘贴造成的后续维护噩梦。需要特别强调的是this(...) 在语法上必须写在这个构造函数签名后面、方法体大括号之前而且它前面不能加其他语句。这一点是 C# 编译器定的规则不是你的选择性问题。如果你想让一个构造函数先做点自己的初始化、再调另一个构造函数那是做不到的——必须先调 this(...)再进入方法体。如果确实需要灵活初始化更合理的方式是走工厂方法或静态方法class TcpClientWrapper { public static TcpClientWrapper Connect(string host, int port, int timeout 3000) { var wrapper new TcpClientWrapper(host, port); wrapper.ConnectWithTimeout(timeout); return wrapper; } }工程上我见过很多人滥用构造器链把六七个构造函数垒在一起每个参数都不一样读起来比不看还累。我的个人标准是构造参数如果超过三组或者某个参数之间没有自然的“默认 - 升级”关系就直接用命名参数 对象初始化器或者提供带语义的静态工厂方法别硬把 this(...) 用成炫耀技巧的手段。3.2 扩展方法第一个参数前那个 this 才是灵魂扩展方法应该是最容易被忽略 this 的场景之一。语法上说扩展方法是静态类里的静态方法只是第一个参数前加了 thispublic static class StringExtensions { public static bool IsNullOrWhiteSpace(this string input) { return string.IsNullOrWhiteSpace(input); } }调用时你可以直接写string s ; bool empty s.IsNullOrWhiteSpace();编译器其实将其编译成 StringExtensions.IsNullOrWhiteSpace(s) 的静态调用而那个隐藏的调用目标 s 就是方法体内可访问的 input 参数。读者看到这应该有所触动方法里的 this 关键字和扩展方法参数前的 this 关键字本质上同源同根都是告诉编译器“这个参数在语法层会被当作当前实例使用”。从 8.0 开始扩展方法可以作用于任何类型包括接口、结构体、枚举。这件事在写通用逻辑时极度好用。我在上位机开发里常用的一个扩展方法是把 byte[] 转成十六进制字符串public static string ToHexString(this byte[] data) { if (data null || data.Length 0) return string.Empty; var sb new StringBuilder(data.Length * 2); foreach (byte b in data) sb.Append(b.ToString(X2)); return sb.ToString(); }但警告一句扩展方法虽然“看起来像实例方法”它并不能访问目标类型的私有成员本质还是静态方法。你在阅读或 debug 时如果忘了这层包装很容易产生“它是不是自带访问类内部状态的能力”的误解进而写出依赖扩展方法“包装类特殊状态”的诡异代码最后坑到自己。3.3 索引器 this[int index]给对象一个“下标”this 也可以用来定义索引器。这里 this 不再是指当前对象而是一种声明语法代表“当前类的带参属性访问入口”。无论类、结构体还是接口都能声明索引器class DataBuffer { private byte[] _buffer new byte[256]; public byte this[int index] { get _buffer[index]; set _buffer[index] value; } }这样外部调用 dataBuffer[3] 就像访问数组一样自然。如果你想处理二维映射或者按名称索引也能通过索引器重载实现更多参数形式。索引器参数甚至可以不是 intclass SensorValues { private readonly Dictionarystring, double _values new(); public double this[string sensorName] { get _values.TryGetValue(sensorName, out var v) ? v : double.NaN; set _values[sensorName] value; } }从设计上看索引器非常适合暴露一种“集合访问语义”但本身不是集合的类。比如工业视觉项目里对多个相机的参数做按名称访问时索引器能显著减少使用方代码量让主流程更清晰。不过也别贪杯——一个类里如果又搞了三四个不同签名的索引器可读性反而下降。我的原则是当你能用属性或者方法表达清楚得更好时就不要强上索引器。3.4 ref 返回与 ref 局部变量让 this 更“锋利”从 C# 7.0 开始this 在索引器、属性和方法中还能配合 ref 返回使用。这带来了一个很“性能向”的用法把对象内部某个字段的引用直接暴露给调用方让调用方不经过属性拷贝直接修改原值。例如有一个数组作为底层存储的类class SampleStorage { private float[] _samples new float[1000]; public ref float GetSampleRef(int index) ref _samples[index]; } var storage new SampleStorage(); ref float s ref storage.GetSampleRef(10); s 42f; // 直接修改 storage 内部 _samples[10]这里表面上没写 this但方法解析时其实还是通过 this 确定了对象实例并返回了 _samples[10] 的托管引用。ref 局部变量可以一直持有着这个引用之后的操作就少了冗余的数组索引检查在热路径性能比较高时有用。但不要为了炫技硬上——ref 返回让调用方可以直接操作类内部状态破坏了封装一旦出 bug 排查起来很痛苦。我建议只在私有辅助方法或经过严谨设计的内部结构中使用公开 API 慎用。3.5 在委托、事件、LINQ 里 this 作为参数传递事件委托中你写事件处理方法时用的 sender 参数实际上就是 this 的一种“化名”。比如button.Click OnButtonClick; private void OnButtonClick(object sender, EventArgs e) { var btn sender as Button; if (btn ! null) btn.Text 已点击; }当按钮触发点击事件时sender 就是触发事件的对象——从窗体类内部看这个对象和 this.button1 是同一个实例。理解 “sender 就是 this只是通过参数传出来” 能帮助你写出更通用的事件处理方法。比如多个按钮共用同一个事件处理函数时你可以依赖 sender 区分到底点了哪个按钮。这是 Winform / WPF / ASP.NET 里最常见的实践之一。LINQ 表达式中的 lambda与 this 的关系大多数情况下是“捕获”关系lambda 内访问当前实例的字段或方法时编译器会生成一个闭包类把 this 保存进去。很多不好排查的内存问题、事件重复注册导致的多次执行问题根源都出在这个隐藏的 this 捕获上。尤其是事件订阅时class MainViewModel { public void Subscribe() { Service.Instance.DataReceived (s, e) Handle(e); } }如果 Subscribe 被调用多次每次都会创建一个新的闭包对象并捕获 this然后重复订阅。解决办法之一就是缓存 lambda 字段private readonly EventHandlerDataEventArgs _handler; public MainViewModel() { _handler (s, e) Handle(e); } public void SubscribeOnce() { Service.Instance.DataReceived _handler; }这是一个非常典型的 this 捕获相关坑。知道了 this 在闭包生命周期里的角色排查这类事件的多次触发问题就快得多。4. 那些容易踩坑的 this 陷阱与边界条件4.1 值类型里修改 this 字段是特性还是坑前面提过在 struct 内部this 可以被理解成“当前变量的引用”因此值类型方法内修改 this 字段本质是修改原变量。如果你对一个定义在数组里的结构体元素调用方法会怎样var points new Point[10]; points[0].Move(1, 2);这在 C# 里是允许的因为索引器返回数组元素地址方法调用会直接引用该元素进行修改。如果换成 ListPoint 呢var points new ListPoint(); points.Add(new Point()); points[0].Move(1, 2); // 可能你预期修改了元素实际没有修改成功原因是 List 的索引器返回的是元素的副本Move 方法修改的是那个临时副本原列表里的点仍然纹丝不动。这个坑我见过不止一次类似场景在各个 C# 论坛的提问中反复出现。解决方案通常是让 Move 返回新值并重新赋值或者把 Point 改成 class又或者使用数组而不是 List。实际工程项目里我的建议是凡是在容器中存放并需要原地修改的数据直接用 class别在 struct 上硬扛。性能收益往往远小于维护成本和认知成本。同样readonly struct 和 readonly 成员修饰符对 this 也有影响。在 readonly 修饰的方法内this 会被视为只读变量任何通过 this 修改字段的行为都会触发编译错误这限制了一些比较“暴力”的写法。对普通开发者来讲知道这一点有助于理解为什么一个结构体方法明明看起来逻辑没问题编译却提示无法修改。4.2 命名冲突this 是解决歧义的钥匙但不是银弹this 最传统的用途是区分参数与字段。类似这样public void SetName(string name) { this.name name; }这个场景最简单日常最多。但如果你以为加上 this 就万事大吉那就错了。假设基类和派生类都有同名字段或属性class Animal { public string Category { get; set; } } class Dog : Animal { public string Category { get; set; } public void Print() { Console.WriteLine(Category); // 输出 Dog.Category Console.WriteLine(this.Category); // 仍然输出 Dog.Category Console.WriteLine(base.Category); // 输出 Animal.Category } }在派生类里 this.Category 只会绑定到当前类的成员即使当前类没有定义 Category这个绑定也会去基类找。如果你以为 this 能强制访问基类同名成员那一定困惑于每次都拿到派生类的值。此时要用 base 关键字而不是 this。写出正确的多态字段访问需要先弄清类型成员的隐藏规则。另一个有趣场景是在构造函数中使用 this 时结合静态成员或实例初始化顺序。实例字段初始化器会在构造函数体内语句执行之前运行而 this(...) 是发生在字段初始化器之后的。这意味着 base(...) 和 this(...) 的调用时机在不同层级之间有一定顺序要求。多数情况下不用太细究但如果你读过一些由代码分析工具带来的“构造函数中包含虚成员调用”警告应该能明白顺序绕行的意义。4.3 为什么静态方法、静态类、匿名类型里没有 this静态方法和静态类中写 this 会编译报错 CS0027原因很简单静态调用无需传递实例自然没有“当前对象”。匿名类型其实也有类似情况。匿名类型是 sealed 类但它的成员都是只读属性这里不会出现 this 关键字因为匿名类型没有显式定义行为方法不存在 this 方法内用法的诉诸场景。扩展方法还有一个特殊情境静态类中的扩展方法照样不可以在扩展方法体里访问目标实例的其他非公开成员。表面上你能使用 this 传入实例但它只是静态方法的一个普通参数而已。不认清这一点在写封装时容易错手写出“我明明已经拿到了 this为什么还访问不了它的 private 字段”的疑问。再看一个比较反直觉的点this 关键字可用于扩展方法的第一个参数但不能再在同一个扩展方法里用它引用别的内容因为此 this 实际是参数名修饰。例如public static string Double(this string s) s s;s 就是“被扩展的实例”方法体里要用参数名 s而不能用 this。有些新手以为在扩展方法内部还能照旧把 this 当“当前对象”一写就报错——这正好加深了“this 就是第一个参数”这一层理解。4.4 和 Java 的 this 相比C# 多了哪些“私货”Java 和 C# 语法很像this 关键词基本作用也类似都表示当前实例。但 C# 的 this 能用在构造器链、索引器声明、扩展方法这几个场景Java 则用 this(...) 只能实现构造链没有索引器语法、也没有真正的扩展方法只能用静态工具类做替代。这说明 C# 把 this 的使用范围设计得更广也更强调类型自身的“可调用”和“可扩展”能力。如果是从 Java 转 C# 的读者我建议你尤其注意扩展方法和索引器这两个差异点因为它们最影响代码风格。Java 里你习惯写 Objects.requireNonNull(x)C# 里更自然的做法是扩展方法 x.RequireNonNull()。代码表达更内敛但第一次在“非本类定义的方法”内部理解 this 时会有错位感认清楚本质后就迎刃而解了。5. 从 this 延伸出去的编码习惯与代码嗅觉5.1 用“谁在调用”的角度看待 this 提升代码嗅觉当你习惯了从“隐藏参数”的角度理解 this 后解读代码的方式会发生改变。以前看一个成员方法可能只关心它的返回值和工作流现在你会更关心一个问题这个方法有没有副作用地依赖或修改了对象自身状态更进一步看到一段在方法内频繁使用 this 来访问成员变量的代码你会自然产生“这段逻辑是否过度耦合实例状态”的警觉并从方法设计角度思考是否应该拆成更纯粹的静态工具方法或独立对象。举个例子我之前维护过一个上位机通讯类它有 Send(byte[] data)、ReceiveAsync()、ParseFrame(byte[] bytes) 三个方法。ParseFrame 里调用了 this.currentChannel、this.frameQueue.Push() 等一堆实例成员导致单元测试时无法单独测试协议解析逻辑。后来我把 ParseFrame 改成静态方法或者带有纯输入输出的方法完全不需要 this测试难度立刻断崖式下降。不要小看 this 出现频率对代码可测试性的提示价值——这是很多团队做代码重构时一个轻松可行的入口指标。5.2 什么时候该写 this什么时候不写团队规范里常有“是否强制写 this.前缀”之争。坚持写的人认为提高可读性反对的人觉得冗余。我在中大型项目里的长期感受是宁可统一风格也不要混用。只要编译器为你做好了变量名遮蔽的检查字段与参数同名却没有写 this 时其实是非常容易出错的写法——因为你以为在给字段赋值实际却把局部变量赋给了自己。这种 bug 在复杂一点的方法逻辑里只靠人肉看很容易漏掉。所以在能显式写 this 区分参数和字段的情况下我的个人习惯是倾向显式写出 this。尤其当构造函数、属性和方法中参数名与字段名高度相似时this 前缀就像给读者一个信号这里改的是实例状态不是局部临时物。而当你调用某个实例方法或访问某个实例属性时如果不涉及歧义不写 this 当然可以代码更简洁。最终选择看团队规范关键是保证同一代码库内风格一致。5.3 进阶联想this 与闭包捕获、异步状态的纠缠最后再聊一个和 this 没那么直接、但又脱不开关系的点异步方法中的 this。一旦你在 async 方法里通过 this 访问了字段这段逻辑就可能在某个未来的时间点被续延执行。这里的“this 引用”会被异步状态机捕获时刻对象生命周期不结束引用就不会释放。这一点尤其容易形成事件循环里的长期引用链。例如某个服务类把自己订阅到一个全局静态事件里它的实例方法里用了 this 访问实例状态那 GC 根通过静态事件 - 委托 - 实例 this 这一整条链就把对象拖住了。很多人排查“为什么对象回收不了”“为什么内存不断上涨”时最终会定位到相似的模式上。处理方法是在不需要时解除事件订阅或用 WeakReference 包装 this 再订阅。这里的 this 已经不是语法问题了而是对象生命周期管理的一根引线。同样在 async void 事件处理器中this 捕获后如果抛异常异常会被推向同步上下文也可能让程序状态难以预判。每当写出这样的代码时把 this 换成一个显式变量名并想清楚这个对象存活多久事件还链在谁身上往往能提前规避几个疑难 bug。6. 把 this 放进调试、反射与性能里看6.1 调试时如何观察 thisVisual Studio 调试器里的“局部变量”窗口通常能看到名为 this 的项其值就是当前实例。在非实例方法的上下文中调试器则不会给你显示 this。这个小细节可以帮助我们迅速判断当前断点是不是停在了真实例方法里。曾经有一次我怀疑一个静态方法被调用后改了某个实例值后来打断点发现根本不在实例方法里于是引发了一个思考——方法设计是不是有问题。如果做更底层的调试你可以通过 SOS 扩展在 dump 分析时查看线程栈上的 this 参数也可以通过 WinDbg 的 !clrstack -a 看到方法的参数列表里包含 this。这种方式能帮你定位一些非常隐蔽的内存泄漏或悬空引用问题。把这些工具和 this 的调用约定结合来看你会发现调试体验上了一个层次。6.2 反射调用与 this 参数的类型匹配上面提过 MethodInfo.Invoke 的第一个参数如果针对实例方法就要传对象实例。如果传错类型的对象呢运行时会抛 TargetException。这类报错表面上和 this 无关但排查时如果能意识到“Invoke 的第一参数就是 this”就能立刻明白为什么传入一个基类实例有时可以、有时不行——这完全取决于你要调用方法的声明类型和实际实例是否兼容。反射中也存在获取扩展方法的情况。扩展方法这个 this 参数在反射里就是一个普通参数没有任何特殊标记。但你若用编译后的静态调用方式去 Invoke 它仍然需要把扩展目标作为普通参数传入。所以很多封装库在处理“给类型附加方法”时本质上都是先判断第一个参数是否有 ExtensionAttribute再用静态方法调用方式去执行而不是实例调用。理解这一点后你自己在做代码生成或 AOP 相关工作时会顺手很多。6.3 性能this 传递会有成本吗这里可以放松一下神经在 JIT 优化之后实例方法调用上的 this 传递几乎不会产生额外的运行成本。值类型方法中 this 按引用传递也不会导致装箱前提是被调用方法确实被 JIT 识别为非虚方法且调用目标类型是确定的。C# 里对 readonly struct 参数可以避免防御性拷贝这在 .NET Core 3.0 以后的意义更为明显。常规业务代码不需要为了“优化 this 的传递性能”而搞特殊设计先把可读性和正确性抓好。但有一个点值得注意当你把一个值类型实例通过接口引用来调用方法时通常会触发一次装箱装箱后的 this 就是堆上副本的引用。这个特性与值类型的修改语义混合在一起会产生前面“List 元素调用 Move 没有生效”类似的隐蔽问题只不过这次的根因是接口分派。尽量避免让 struct 实现接口后又期待它的方法能修改原始变量这一条对工业设备和游戏领域做高性能数值运算时尤为重要。从编码、调试到性能分析this 这个关键字几乎贯通了 C# 程序运行的主链路。它不是英语里 that 的对应物那样只承担指示功能而是一个承载了实例方法调用机制、构造链设计、类型扩展能力与值类型可变性的枢纽。日常写码时养成留意 this 的习惯往往能顺藤摸瓜地提前识别出很多设计层面的隐患这大概是它带给我最大的收益。以后你再看到代码里这个小小的关键字不妨多想一步这个 this 出现在这里究竟是因为语言机制的要求还是代码设计本该如此
上一篇/下一篇内容由系统自动关联 返回资讯列表 →