惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
C
CERT Recently Published Vulnerability Notes
P
Proofpoint News Feed
Vercel News
Vercel News
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
Help Net Security
Help Net Security
月光博客
月光博客
N
News and Events Feed by Topic
Cloudbric
Cloudbric
博客园 - 司徒正美
L
LangChain Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tenable Blog
The Register - Security
The Register - Security
The Hacker News
The Hacker News
I
InfoQ
The Last Watchdog
The Last Watchdog
MyScale Blog
MyScale Blog
Schneier on Security
Schneier on Security
WordPress大学
WordPress大学
小众软件
小众软件
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
K
Kaspersky official blog
L
LINUX DO - 热门话题
N
News | PayPal Newsroom
F
Fortinet All Blogs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security @ Cisco Blogs
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
H
Help Net Security
Google Online Security Blog
Google Online Security Blog
S
Schneier on Security
C
Cisco Blogs
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Latest news
Latest news
PCI Perspectives
PCI Perspectives
T
The Blog of Author Tim Ferriss
P
Palo Alto Networks Blog
T
Tor Project blog
Project Zero
Project Zero
云风的 BLOG
云风的 BLOG
Webroot Blog
Webroot Blog
Attack and Defense Labs
Attack and Defense Labs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org

机核

游戏性能旗舰最强之选,一加 Ace 6 至尊版国补到手价2999元起 6元钱自己更换电动车刹车线 《生化危机9:安魂曲》编剧Haris Orkin专访 摸金游戏?音乐游戏!暗区新赛季这把能弹的琴有何来历? 好评国产武侠SRPG《息风谷战略》免费DLC现已推出 | 机核 GCORES 碎片 《生化危机:安魂曲》将于5月实装首个免费更新内容 | 机核 GCORES 新锐东方游戏,谱写世界新章! 沉浸式恋爱视觉小说游戏《心象演算》免费试玩版现已正式上线 | 机核 GCORES 互动影游《代号三国:龙起》上线!穿越三国与曹操并肩、与佳人同行、与权谋博弈! 《老头收集梦想生活》,游戏酒桌会6,录音笔VOL.689 | 机核 GCORES 破界·共生——《白日梦:无限世界》五大核心游戏特点解析 价格已到史低,锐龙5 9600X/锐龙7 9700X正适合抄底 时间循环之旅即刻启程!《归环》一周目测试今日开启 热门在线韩游变魂游,洛奇衍生作能否打破“花瓶”魔咒? LG UltraGear evo 全新高端显示器系列:当“5K”遇见“AI”,不止强大,更懂热爱 反套路三国互动影游《代号三国:龙起》今日上线! 愿望单登记人数突破10万!备受瞩目的“女儿养成游戏” 《梦幻魔法公主》今日于Steam平台上线!限时八折优惠中 《黑神话:悟空》全球音乐会2026巡演将于4月29日12时开票 | 机核 GCORES “Snowguelike”生存肉鸽挖矿新作《蛙穿雪境》公布发售日期,5月7日正式上线 | 机核 GCORES 重塑移动办公、AI创作新境!全新华硕灵耀Air系列、ProArt 骁龙版震撼首发,创芯未来 均分88:《Saros》媒体评分汇总 | 机核 GCORES 上海烛龙公布合作遗迹探险游戏《吉时已到》首支预告片 | 机核 GCORES 首个独立游戏《萝薇日记》已上线Steam! 烛龙新IP《吉时已到》首曝,打造国内首款中式合作遗迹探险游戏 喜加一:《暗黑破坏神Ⅳ》国服现已开启限时免费领取本体活动 | 机核 GCORES 新版《生化危机》电影定于9月18日上映,官方网站现已上线 | 机核 GCORES 《冲就完事模拟器2》“星球大战”联动DLC正式宣布 | 机核 GCORES SteamController将于5月4日发售,售价99美元 | 机核 GCORES 事已至此,内存这么用也算省钱了,“2+1”非对称双通道应用实测 505游戏母公司现已收购《明末:渊虚之羽》IP | 机核 GCORES 基石 手动杂谈12|格斗游戏也能讲好故事 | 机核 GCORES I Love You Mr Snowball 我的向日葵小姐 会比GTA6先发售吗?最硬核的生存游戏《DAYZ》要推新DLC了 周记02:在2026遇到新怪谈 什么硬件,能让游戏Loading界面快速消失? | 机核 GCORES GadioSpec《百年风云世界杯》免费试听集 | 机核 GCORES 百年风云世界杯Vol.1丨足球崛起 | 机核 GCORES 100年前,一群人提前替我们经历了AI恐惧 | 机核 GCORES 《呼啸山庄》2026 ——一辆当代艺术的大卡车冲撞了我的大脑 手游《天穗之咲稻姬:日之香巡灵传》宣布将于7月27日停服 | 机核 GCORES 集结梦之队,征战世界杯,《最佳球会ONLINE》上线Steam 山水绝景随心拼 休闲建造游戏《千里山河录》Steam商店页公开 巫师帽、法袍、魔杖,为什么它们是影视、游戏里的法师必备三件套? 《无鞘信使》-第一章 复古风自动战斗肉鸽《终结之终结》Steam商店页面现已上线 电脑里有一款不破不立的MMO,录音笔VOL.688 | 机核 GCORES 《生化危机》30周年纪念周边发售,这次是真的“保护伞” 可靠耐用+AI全能,惠普战66 2025锐龙版深度体验 《时之书:无尽终章》关卡“大航海时代”全球首次公开 经典名作《乌龙派出所》改编经营模拟游戏《乌龙派出所~阿两的商店街物语~》正式宣布支持简体中文 明日开冲,解锁反套路三国互动影游《代号三国:龙起》即将上线 独立游戏《这是我的宝藏!》已发售~ 降低难度不是唯一解,无压力死亡也是好体验 Netflix官宣新片《普通人》:讲述韩国现代史上的权力风暴 《生化危机:安魂曲》全球销量现已突破700万份 《绝地鸭卫》PC版5月15日正式发售 亡妻回忆录?女性向情感叙事游戏《S-mail》现已正式发售 全新酷黑风格,酷睿Ultra 200S PLUS的高性价比搭档!七彩虹BATTLE-AX B860M-PLUS S WIFI7 V20 超级黑刃主板测评 胖狗 索尼发布了港台地区PS5产品价格调整公告,将于5月1日起实施 二次元怪猎+性感美女!《碧蓝幻想:无尽黄昏》开启Beta公测 这款怪谈类型中式恐怖游戏居然更新了?! 《百日战纪 -最终防卫学园-》改编漫画将于今年冬季开启连载 《如被附身,请致电我们》:匈牙利黑色幽默恐怖小说 《竹屿山房杂部卷五》(译文) 大树 超越引擎 摄影分享丨四月 碎片杂记vol.72 当老式FPS与老式动画技术碰撞出新时代的火花—《神探杰克鼠》 想做独游,如何避免首个项目就褒姒? 在线多人动作游戏《OCTOPinbs》将于5月12日上线Steam! 《酒鬼女神的酒诡》确定将于2026年登陆Steam 《CRYMELIGHT》将于11月5日(周四)正式发售!4月25日开始预购! 钢铁国度MKI部落Evolution,蛮兵部分 【钢铁国度】部落-Primal MK I,蛮兵部分 这是一个高中生用ai跑出来的作品,我自称他为物理神话 钢铁国度MKI部落Metamorphosis,蛮兵部分上 钢铁国度MKI部落Metamorphosis,蛮兵部分下 INDIE Live Expo于4月25日举办:首发9款新作,超200款独立游戏亮相 暗黑卡牌策略新作《魔忌:穷鼠啮狸》发布全新中文试玩版和发售预告片,4月30日正式上线在即 战锤40K长篇小说:变节者・苦难主宰(三)(全书完) 战锤40K长篇小说:变节者・苦难主宰(二) 战锤40K长篇小说:变节者・苦难主宰(一) 英语语言学习丨短语专题1:短语的特点 唯一的EVE战争 【少前同人】【M200】战术人形会梦见音乐会吗 【昏迷3】即将发售,前作主角悉数到场!“恶灵”宋老师化身可操控角色 业内人士:游戏公司“十有八九”使用生成式AI,包括卡普空 欢庆一周年:《光与影:33号远征队》全新纪念艺术图、超值折扣与免费更新同步上线 才刚刚开始呢 【抽奖】《星际卡车司机》推出免费大型更新,四折平史低折扣进行中 四人合作FPS游戏《佣兵猎手》抢先体验重大更新 1 现已上线 Raw Fury新作《深馅地牢 Deep Dish Dungeon》将于今秋加入 XGP 塔防幸存者游戏《魔怪来袭》推出首个 DLC 《饿狼传说:群狼之城》1周年纪念!新DLC“沃尔夫冈·克劳萨”今日参战 猫狗相伴 欢乐闯关 双人合作平台跳跃游戏《猫狗同行》上线Steam商店页 《同行:月球逃脱》(Together: Moon Escape)上线将于明日上线Steam
C# with Godot:物以类聚
cameLcAsE · 2025-07-30 · via 机核

虽然在很多地方GDScript看起来并不像典型的面向对象语言,但是总的来说它的接口还是Godot API都比较面向对象。只不过作为一门为了让开发者能够快速上手的语言它有意无意地弱化了很多面向对象的设计。我们先来复习一下GDScript中是怎么对待类的。

GDScript中的类

我们新建脚本总是从一个GDScript文件开始,同时还要选择一个基类。实际上来说,一个脚本中编写的代码就定义了一个类,脚本文件本身也是Godot的一种资源(Script继承了Resource)。

不过只是这样并不能让它表现得像一个传统面向对象语言中的一个类那样,比如我们还不能让它出现在类型标注中。为了让一个脚本可以当成一个类型用,我们得在脚本中写一句:

class_name Player

这样我们就可以给其它变量标注为:

var player: Player

当然,你标不标这个类型很多时候也不影响代码的执行,毕竟GDScript的静态类型主要还是为了做静态类型检查和代码补全,它并没有那么严格的类型系统。

GDScript也有class关键字,可以定义“类”。但是这个类实际上只是定义在脚本内部的一个所谓内部类(inner class),用起来有一点点别扭。如果要在其它类(脚本)中使用这个类作为类型,那么需要先把定义这个内部类的脚本写上class_name,然后通过脚本的类名拿到这个类,共享起来有点麻烦。由于GDScript没有命名空间,也没有静态类,要组织一系列这种内部类的话,可能就需要注册一个Autoload/Global脚本了。

如果要真的模拟一个“正常的”数据类(非节点类),你能做的其实比较有限。你需要单独建一个脚本然后选择更抽象的基类,比如RefCounted(可以让你免于手动管理内存),然后再用class_name把它作为类型暴露出来。

在C#中,“类就是类”,教科书上的东西这里就不过多重复了。但还是“简单说两句”:

  • 类就是一个模子,对象就是从这个模子里倒出来的具体的东西

  • 类是状态(数据)和行为(方法)的封装(encapsulation),这些行为会改变类的状态

或者更加粗野地说:类就是一种复合数据类型,把一堆变量放在一起然后假装它就是一个东西(object),然后用语法糖把一些可以操作这些东西的函数包装成可以通过这个东西直接调用并修改那些变量。

但是还是要严谨一点说的是,上面说这些只针对一个“类”本身,而不能概括整个“面向对象编程”。面向对象编程(太长了,懒得打,后面可能回合OOP交替使用),本身不止一种基于类的范式,而且如果你要原教旨主义一点,现在的基于类的范式和早期的“消息传递”机制也差得很远。

言归正传。你可以在单独的C#源文件中新建一个类,也可以直接在你的Program.cs中编写一个类(记得放在可执行语句之后):

class Player {}

这就是一个合法的、最简单的类,没有任何成员——成员就是在类中定义的各种变量、方法、常量等元素。成员需要通过类或者对象通过“成 员 访 问 运 算 符”(说白了就是点.)来访问。

类和源文件不是一对一关系

C#不需要你一个文件定义一个类。当然,出于代码整洁和可维护性方面的考量,你也可以这样做。IDE中创建新类之类的选项一般也会直接给你创建一个和类名同名的源文件。

源文件里可以写些啥

前面提到过,为了方便开发者,现在用dotnet命令行工具直接新建项目会默认在Program.cs中使用顶级语句。如果你新建了另一个文件,你就没法随便写一些可执行语句了。如果你这样做了,就会收到8802错误,意思就是说一个项目中只能有一个编译单元(可以理解为C#源文件)有顶级语句。

不使用顶级语句的源文件中,实际上只能有各种类型定义。

命名空间

如果你使用的是各种IDE内置的选项新建一个类,你可能会在对应的源文件中发现这样的代码:

namespace MyProject;

这个东西是命名空间的声明,表明此文件中定义的各个类型都属于这个命名空间。

命名空间,或称名字空间(namespace)主要是用来组织代码(或者说类型),避免重名。

例如你刚刚定义了一个表示玩家的Player类,然后你用了一个第三方库里面有一个播放音乐的Player类,这就会造成命名冲突。命名空间可以避免这个问题。在一个命名空间中无法直接用类名来访问定义在另一个命名空间中的类型。

较早的C#中要求命名空间声明之后必须跟一个代码块,代码块内部的各元素才属于这个命名空间。当然这种语法现在仍然有用。如果你需要在同一个文件中声明不同的多个命名空间,就必须用花括号。

命名空间其实更多地是为了便于组织代码结构,没有那么多严格的要求。你可以在同一个文件中多次使用同一个命名空间来囊括类型,也可以在不同文件中使用同一个命名空间。

命名空间的嵌套

命名空间也可以用点语法声明层次更深的命名空间:

namespace UI { class Foo{} } namespace UI.Components { class Bar { Foo foo; } }

层次比较深的命名空间中,使用同一命名空间链条上的、其它命名空间中的类型,不需要打出全名。

省略命名空间

一旦一个类型归属于某个命名空间,它的全名实际上就变成了命名空间.类名。在其它命名空间中使用这个类型时你就必须打出它的全名。

当然,这很麻烦。所以有using关键字来帮忙。以.NET标准库中JSON转换方法为例,如果想把一个对象转化为JSON字符串,你需要用到JsonSerializerSerialize方法,它的全名是:

System.Text.Json.JsonSerializer.Serialize

JsonSerializer位于命名空间System.Text.Json之中,写全的话太长了。如果在源文件顶部写上:

using System.Text.Json;

你就可以只写JsonSerializer.Serialize

System是一个比较特殊的命名空间,.NET、C#标准库中的各种东西都在这里面,因此在C#中这个命名空间是默认using了的,你不用单独写出来就可以直接免去System使用里面各种东西。比如int本质上是.NET基本类型System.Int32的别名。

C++程序员注意,C#不需要using namespace 命名空间;

new一个对象

类似于在GDScript中使用Player.new()这种语法来构造一个类的实例,在C#(以及很多其它OOP语言)中使用new关键字来实例化(instantiate)一个类:

Player player = new Player();

你可以已经知道这实际上是在调用所谓的构造函数(GDScript中表现为特殊方法_init),后面马上会讲到如何定义。

在比较现代的C#中,实例化类时如果可以推断出类型就可以省略类名:

var p = new Player(); // 用var声明变量 Player pp = new(); // pp声明为Player类型,调用构造函数时省略类名 var p = new(); // 错误!

字段

类似于GDScript中给一个脚本用var定义若干变量(属性),C#类也可以:

class Player { int id; string name; }

这种定义在类中的变量称作字段(field)。

聪明的你可能会觉得,啊我知道了,我可以像这样:

var p = new Player(); p.id = 1;

然鹅,你的编辑器不仅没有提示你id这个东西,甚至还报错了,表示id无法访问。

访问级别

实际上传统一点的OOP语言会对类的成员(字段、属性、方法啥的)的访问级别(access level)进行控制。也就是说,某个成员能否在类之外、能被哪些类访问。之所以要这么做是因为根据面向对象编程的一些指导原则,我们应该尽可能地维持类的封装,减少对外部的暴露,但又维持一定的可扩展性。

在C#中,如果一个成员没有指明访问级别,那么就默认为private(私有)。也就是说这个成员无法在类外部被访问。上面的字段等价于声明为:

private int id;

访问级别关键字要放在类型前面。

如果你希望直接访问这个字段,那么就需要用public(公共)级别,表示在任何地方都可以访问它。

静态字段

C#中也可以有静态成员,也和很多编程语言一样用static关键字。静态意味着它不属于具体的实例,而是属于整个类。有些地方也叫类字段:

Console.WriteLine(Player.playerCount); ​ class Player { public int id; public string name; public static int playerCount; }

在类外部,静态成员需要通过类名来访问,在类内部则不需要。

构造函数

构造“函数”(constructor)在一个面向对象语言中说起来貌似有点怪,但是这个基本上是约定俗成的译法了,如果你要说成“构造器”貌似又有点拗口。

构造函数顾名思义用于构造一个类的实例——但是这个名字实际上有一定的误导性。因为构造函数并不负责划一块内存并放入各种东西,你在这里做得最多的事其实就是初始化对象各个实例。要说真正负责分配内存、构造一个对象的不如说是new运算符的具体实现。所以说,其它语言中采用__init___initinit之类的名字可能更贴切:initialize,就是初始化。

正如你在上面看到的,定义类时可以不定义构造函数,你仍然可以用new Player()的方式构造一个实例。此时相当于调用了一个无参构造函数。构造函数就是在类定义中定义的一系列特殊函数,它们没有返回值,名称为类名

class Player { public int id; public string name; public Player(int id, string name) { this.id = id; this.name = name; } }

除了名称和返回类型特殊之外,其它和定义一般的函数没有区别。

一旦定义了有参数的构造函数,就无法调用无参数的构造函数——除非你自己定义了一个无参构造函数。

这个

this关键字类似于GDScript中的self,表示“这个实例”。this在绝大部分时候都可以省略,上面的代码中我写了出来是因为构造函数的参数和字段重名了。

初始化

在类中声明字段时,可以立即给它们进行初始化。特别是对应类型的默认值不是你想要的值时。

class Player { public int id = -1; public string name = "Anonymous"; // ... }

这些初始化代码和构造函数是独立的,初始化语句会先于自己的构造函数执行。

多个构造函数

你可以给类定义多个参数列表不同的构造函数。“参数列表不同”指的是参数个数和/或参数类型不同,只要调用时没有歧义即可:

class Player { public int id; public string name; public static int playerCount = 0; public Player(string name) { this.id = playerCount++; this.name = name; } public Player(int id, string name) { this.id = id; this.name = name; } }

这里定义了一个只需要传入玩家名字的版本。其id根据当前创建过的玩家实例数量来确定。

马上会讲到,这本质上是利用有关方法的一个特性实现的。

方法

定义在类里面的“函数”就叫方法。在面向对象编程中,一个对象就是状态(字段)和行为(方法)的结合体。行为可以修改对象的内部状态。

class Player { // 省略字段定义 public void Greet() { Console.WriteLine($"Hello! I'm {name}!"); } }

方法是类成员的一种,也需要指明访问级别。方法可以直接访问类中定义的其它成员,没有歧义的时候也不用加this

静态方法

方法同样可以是静态的,也就是说它不访问具体实例的成员(不修改某个对象的内部状态)。静态方法在类外同样需要通过类名调用,类内部可以直接调用。

方法重载

重载(overload)听起来很炫酷,但是实际上意思就是允许同一个名字的方法可以有多个参数类型不同的版本。例如Console.WriteLine在.NET 9中有19个不同的版本

很多语言实际上不支持方法/函数重载,也没啥问题,主要是取名字比较麻烦。

静态类

类和成员一样可以是静态的。静态类的特殊之处在于它不能被实例化,它只能包含静态成员和常量。

这样的类一般是一些工具类,常用于容纳一些不需要维护内部状态的方法。例如我们一直在用的Console.WriteLine就是静态类Console的一个静态方法。

另一(二)个比较常用的静态类是System.MathFSystem.Math。里面包含了很多常用的数学常量和函数,比如EPI、各种三角函数。区别在于MathFfloat为返回值,另一个是double

属性——封装字段

字段有什么问题呢?

如果直接使用字段,我们实际上无法精细的控制字段的访问。如果标记为private,外部无法修改它,但是也没法读取它的值;如果标记为public,外部虽然可以读取它了,但是也就意味着可以随意修改它。还有就是我们无法在访问字段时执行一些额外的代码,完成一些额外的工作。

聪明的你可能想到了,我们可以直接写一个public方法来提供对private字段的访问:

// Player的定义中 // 假设id和name为private的字段 public int GetId() => id; public void SetName(string newName) { Console.WriteLine($"Player {id} changed name from {name} to {newName}"); name = newName; } public string GetName() => name;

只给id编写了一个GetId方法,那么就相当于它是一个只读的东西。而name是外部可读可写的。

实际上在很多语言中就是这么干的,这些方法被称为getter和setter。别忘记了,我们在GDScript中可以写:

var name: String: set(value): name = value get: return name

C#中也有类似的语法,那就是属性(property):

private string name; public string Name { get { return name; } set { Console.WriteLine($"Player {id} changed name from {name} to {value}"); name = value; } }

属性的定义的头部和字段类似,有访问级别、类型、名称。紧接着是一个代码块,里面可以包含两部分内容,也就是getter和setter。getter以get关键字开头,它实质上就是一个返回类型和属性类型匹配的无参方法,它的主体可以是任何内容。

类似地,setter以set开头。你可能会问,setter应该是一个“带有一个参数,且类型为属性类型的方法”,那么它的参数呢?请看value。你可能在GDScript中编写setter的时候按照惯例将它的参数取名为value,实际上在C#中,具体到setter中,value是一个关键字(这是一个所谓“上下文关键字”,也就是说在具体的语法结构中才会被识别为关键字)。它就是给属赋值时收到的值。

定义了这样一个Name属性之后,我们就可以:

var p = new Player(); p.Name = "John"; Console.WriteLine(p.Name);

属性在使用方法控制字段存取的同时,可以保持类似直接使用字段的语法。

根据具体的需要,属性定义可以大幅简化。如果只是单纯想写一个可读可写的属性你可以直接:

public string Name { set; get; }

你甚至不需要定义一个对应的字段,setter和getter的主体都可以省略,此为自动实现的属性(automatically implemented property)。

实际上,getter和setter一样可以设置访问级别:

public string Name { private set; get; }

这样一来就得到一个对外只读的属性。

属性和字段一样,可以自己设置初值。虽然看起来有点别扭,但是你直接把初始化的赋值语句放到花括号后面即可:

public string Name { set; get; } = "Anonymous";

递归注意

给GDScript的脚本属性写getter和setter时有个好处就是,你不需要单独定义一个实际保存属性数据的单独的变量(这种变量/字段称为backing field),属性的setter中可以直接给这个属性赋值而不会发生无限递归:

var username: String = "name": set(value): print("set username") username = value get: print("get username") return username ​ func _ready() -> void: username = "Abc" print(username)

这样的代码在Godot中是没有任何问题的。在getter和setter中都是直接访问属性本身而不需要(如果必要你可以)额外定义另一个变量。

public string Username { set(value) => Username = value; // 略 }

但是类似的代码在C#中会造成无限递归。也就是说,尝试设置Username时会调用setter,然后在setter中设置Username的话又会调用setter,就这样无限递归下去会直接导致异常。因此只要不是自动属性的话就一般情况下就还是需要定义一个字段,当然你在类内部可以直接访问字段。

展望未来:消除backing field

实际上在未来很有可能可以像GDScript一样不单独定义一个字段。实际上在目前.NET 9.0 SDK中,如果手动编辑项目文件设置语言版本为preview就可以用到这个特性:

public int Prop { get; set { field = value; } }

这里getter直接省掉了,然后在setter中直接用field关键字指代backing field。

计算属性

你可能会看到计算属性(computed property)这种说法。

class Circle { public float Radius {private set;get;} public float Area => Radius * Radius * MathF.PI; }

也就是说某个属性的值实际上单纯就是对象其它的属性算出来的,因此这种属性首先是一个只读属性。对于只读属性,可以直接用表达式主体写法直接返回一个表达式。注意和用表达式主体定义的方法进行区分,用表达式主体定义的无参方法也得有一个括号表示参数列表,只读属性没有。

泛型类

类和方法一样也可以是泛型的。比如有这样一个盒子,什么都可以装:

class Box<T> { private T value; public T Value => value; public Box<T>(T value) { this.value = value; } }

类似于泛型方法,泛型类也用<T>的形式指明一个类型参数。

这里只简单介绍一下,我们会在后面看到真正的泛型类到底是怎么用的。