本文主要来自<<C#实践入门>>哈里森.费隆 著,仅用为做笔记。
编写正确的C#代码
代码行的功能类似于句子,这意味着它们需要具有某种分隔或结束字符。在C#中,所有被称为语句的代码行都以分号结尾,分号用于将语句分开,以便代码编辑器进行处理。
与通常情况下的书面文字不同,C#语句从技术上讲不必位于一行。代码编辑器怒感空格和换行符。例如,一个简单的变最可以有不同的写法。可以按如下方式进行编写:
public int firstName "Bilbo";
也可以这样编写:
public
int
firstName
=
"Bilbo";
以上两种写法 Visual Studio 都可以接受,但是强烈建议不要使用第二种写法,因为这会使代码极难阅读。
方法或类后面的花括号的标准格式是在新的一行包含每条语句,如下所示:
public void MethodName ()
{
}
但在创建新脚本或者在线访问 Unity 文档时,你会看到第一个花括号与声明位于同一行:
public void MethodName() {
}
虽然这并不是什么让人抓狂的事情,但尽早理解这些细微差别是很重要的。
简单的调试技术
在处理实际问题时,我们需要一种方式来把信息和反馈打印到控制台。为此,人们引入了调试的概念,C#和 Unity 提供了简化调试过程的辅助方法。每当需要调试或打印一些信息时,请使用以下方法:
- 对于简单的文本或单个变量,请使用 Debug.Log0方法。文本必须在括号内,
并且变量可以直接使用而无须添加字符:
Debug.Log("Text goes here.");
Debug.Log(yourVariable);
- 对于更复杂的调试,可以使用 Debug.LogFormat0方法。通过使用一对用花括号标识的占位符,可以在打印文本中放置变量。每组花括号都包含一个从 0开始的索引,对应于一个顺序变量。
在下面的示例中,q0占位符被替换为变量 variable1 的值,1占位符被替换为变量 variable2 的值,以此类推:
Debug.LogFormat("Text goes here, add (0) and (1) as variableplaceholders",variablel,variable2);
变量的语法
我们已经看到变量是如何创建的,并且涉足变量提供的一些高级功能,但我们仍然缺少使这一切成为可能的语法。变量不仅仅出现在 C#脚本的顶部,它们还必须根据特定的规则和要求进行声明。变量需要满足以下基本要求:
- 需要指定变量存储的数据类型。
- 变量必须具有唯一的名称。
- 为变量指定的值必须匹配指定的类型。
- 变量的声明必须以分号结尾。
以下语法能够满足上述基本要求:
dataType uniqueName = value;
以上方式简单、整洁、有效。但如果变量只有这一种创建方式,那么从长远看,这不利于 C#语言的发展。复杂的应用程序和游戏都有不同的用例和场景,所有这些都需要使用独特的C#语法。
声明类型和值
创建变量的最常见场景就是拥有所有可用数据时。例如,如果我们知道玩家的年龄,存储起来就很简单,如下所示:
int currentAge =32:
以上语句能够满足变量的所有基本要求:
- 指定了数据类型 imt。
- 使用了唯一的名称currentAge
- 32是整数,与指定的数据类型匹配
- 语句以分号结尾。
仅声明类型
考虑另一种场景:知道变量存储的数据类型和名称,但不知道值。值将在其他地方计算和分配,但仍然需要在脚本的顶部声明变量。
对于这种情况,可以仅声明类型,如下所示:
int currentAge;
以上语句只定义了类型int 和唯一的名称 currentAge,但由于能够满足变量的所有基本要求,因此仍然有效。如果没有赋值,那么默认会根据变量的类型来赋值。在这种情况下,curentAge 默认为0以匹配 int 类型。当实际值可用时,就可以在单独的语句中轻松进行设置:
currentAge =32;
访问修饰符
既然基本语法不再是个谜,现在让我们深入了解变量语句的细节。由于我们习惯从左向右阅读代码,因此从一个我们还没有讨论过的主题开始对变量进行深入研究是有意义的:访问修饰符。
快速回顾一下我们在 LearmingCurve 脚本中使用的变量,你会发现语句的前面都有额外的关键字public。public 就是变量的访问修饰符。可以将其视为安全设置,用于确定哪些对象可以访问变量的信息。
注意:任何未标记为public 的变量都不会显示在 Unity 的Inspector 面板中.
如果加上访问修饰符,那么更新后的语法如下所示:
accessModifier dataType uniqueName = value;
尽管使用访问修饰符对于声明变量来说不是必要的,但对于新手来说,这是个好习惯。
选择安全级别
C#有四个访问修饰符,但是作为初学者,最常用的是如下两个。
- public:对任何脚本开放,不受限制。
- private:变量仅在创建它们的类(称为所属类)中可用。没有使用访问修饰符声
明的任何变量默认都是私有的。
另外两个访问修饰符如下。
- protected:变量在所属类或派生类中可以访问
- internal:仅在当前程序集中可用。
每个访问修饰符都有特定的用例,但是在继续学习之前,我们不用担心 protected和internal。
实践一一使变量私有
就像现实生活中的信息一样,有些数据需要进行保护或与特定的人共享。工个量如果不需要在 lmsprctor 面板中进行据需激进脚本进行访同,那么最好使用private访问修饰符进行声明。
下面执行以下步骤,更新 LearningCurve 脚本:
(1)把 rsNumber 前面的访间修饰符从 public 改为 private, 然后保存文件
(2)返回 Unity,选择 Main Camer,看看 Leaming Curve 部分有什么变化
刚刚发生了什么
firstNumber 现在由于是私有的,因此在 Inspector 面板中不可见,并且只能在Leaming Curve 中进行访问,如图 3-1 所示。如果单击 Play 按钮,那么脚本仍将像以前那样正常工作。
使用类型
为变量分配特定类型是一项重要决定,这会深入影响恋最涉及的每个交互。C#是种强类型或类型安全的语言,这意味着每个变量都有数据类型,同时还意味着当执行运算或者将给定的变量转换为另一种类型时,在在特定的规则必须遵循。
通用内置类型
C#中的所有数据类型都派生自同一个祖先 System.Obiect。这种称为通用类型系统(Common Type System,CTS)的层次结构意味着不同类型有很多功能能够共享。图列出了一些最常见的数据类型以及它们所能够存储的值。
数据类型 | 变量内容 |
int | 整数,比如数字3 |
float | 浮点数,比如3.14 |
string | 字符串,比如"watch me go now" |
bool | 布尔值 |
除了指定变量可以存储的值的类型之外,还可以指定有关变量自身的附加信息,比如:
- 所需的存储空间
- 最小值和最大值
- 允许执行的运算
- 在内存中的位置
- 访问方法
- 基本(派生)类型。
使用 C#提供的所有类型是使用文档而非依靠记忆的完美示例。很快,即使使用最复杂的自定义类型,你也会感觉像是第二天性。
1.实践---处理不同的类型
下面继续,打开LeamingCurve 脚本,在下图 所示的通用内置类型部分为上图中的每一种类型添加一个新的变量。选择名称和值,确保它们被标记为 public,这样才可以在Inspector面板中看到它们。
刚刚发生了什么
在lspector 面板中,不同的变量类现在是可见的,留意显示为复选框的布量(true 表示选中,false 表示未选中),
2.实践---创建内插字符串
数值类型的行为与数学中的一样,但字符串是另一回事。我们通常以S字符开头把变量和字面值直接插入文本,这称为学符串插值(tring intepolatio)。插入的值将被添加到花括号中,就像使用 LogFommat 方法一样。下面让我们在 LeamingCurve 脚本中创建一个简单的内插字符串,看看效果。
// Use this for initialization
void Start()
{
Debug.Log($"A string can have variables Like {firstName} inserted directly!");
}
刚刚发生了什么
由于有了花括号,firstName 变量的值得以在内插字符串中打印出来,如图36月示。也可以使用+运算符创建内插字符串,我们会在后面进行介绍。
类型转换
我们已经看到,变量只能保存它们声明的那种类型的值。但某些情况下,我们需要组合或分配不同的变量类型。在编程术语中,这叫作转换,转换主要有两种形式:隐式转换和显式转换。
- 隐式转换会自动发生,通常发生在较小的值适合另一种变量类型且无须四舍五入的情况下。例如,任何整数都可以隐式转换为 double或float 类型,而不需要编写额外的代码.
float implicitConversion = 3;
- 当转换过程中存在丢失变量信息的风险时,需要进行显式转换。例如,如果想把 double 类型的值转换为整数,就必须在想要转换的值之前添加目标类型到圆括号中以显式地进行类型转换。这相当于告诉编译器,我们知道数据(或精度)可能会丢失。在如下显式转换中,3.14 被四舍五入为 3,失去了小数部分。
int explicitConversion = (int)3.14;
推断式声明
到目前为止,我们已经学习了类型的交互、操作和转换规则,但是如何处理需要存储未知类型的变量的情况呢?这听起来可能有些让人抓狂,但是想想数据下载场景我们知道信息会进入游戏,但不知道它们会以什么形式出现。
幸运的是,C#能够从赋值推断出变量的类型。var 关键字使程序知晓 currentAge变量的类型由值32决定:
var currentAge = 32;
自定义类型
当我们讨论数据类型时,很重要的一点就是:要尽早了解数字和单词(又称为字值)并不是变量可以存储的唯一值。例如,类、结构和枚举都可存储为变量,我们将第5章介绍这些主题,更详细的内容将在第 10章介绍。
类型综述
类型很复杂,熟悉它们的唯一方法就是使用它们。然而,以下一些重要的原则要记住:
- 所有变量都需要具有指定的类型(无论是显式的还是推断式的)。
- 变量只能保存指定类型的值(比如,不能将字符串赋值给 int变量)。
- 每种类型都有一组能用和不能用的运算符(不能用另一个值去减布尔值)。
- 如果一个变量需要用其他类型的变量进行赋值或组合使用的话,那么需要对它们进行转换(隐式转换或显式转换)。
- C#编译器可以使用 var 关键字从变量的值推断出变量的类型,但仅应在变量的类型未知时使用。
命名变量
我们已经学习了访问修饰符和类型,对变量进行命名似乎是事后才想到的事,这并不是草率的选择。清晰且一致的命名约定不仅能使代码更具可读性,而且能确团队中的其他开发人员无须询问就能理解代码编写者的意图。
最佳实践
命名变量的第一条则就是变量名要有意义,其次是要使用驼峰式命名风格。在戏中,如果声明如下变量来存储玩家的健康状况:
public int health = 100;
你的脑海中应该会出现一连串的问题。谁的健康状况?存储的是最大值还是最值?当值发生更改时,其他代码会受到什么影响?这些问题都可以通过使用一个有意)的变量名来轻松地回答。
使用如下方式命名变量可能更合适一些:
public int maxCharacterHealth = 100;
注意:请记住,使用驼峰式命名风格的变量名以小写字母开头,然后每个单词的首宇母大写。驼峰式命名还明确区分了变量名和类名,后者以大写字母开头.
这样就好多了。稍加思考之后,我们更新了变量名的含义和上下文。由于对变量名的长度没有技术上的限制,你可能发现自己有些过头,写出了可笑的描述性名称,这肯定会给自己带来问题,就像短的非描述性名称一样。
作为一般规则,变量名应具有一定的描述作用---不要太长,也不要太短。找到自己的风格并坚持下去即可。
变量的作用域
我们即将结束对变量的讨论,但还有一个更重要的主题需要介绍:作用域。类似于访问修饰符确定哪些外部类可以获取变量的信息,变量的作用域用于描述给定变量的可用区域。
C#中的变量有三种级别的作用域,如图3-7 所示。
- 全局作用域是指变量可以在整个程序中(在本例中是游戏)访问。C#不直接支持全局变量,但这个概念在某些情况下很有用,我们将在第 10 章介绍。
- 类或成员作用域是指变量在所属类的任何位置都能访问。
- 局部作用域是指变量只能在特定代码块内访问。
注意:我们所说的代码捷是指任何一对花括号包含的区域。这些括号在编程中作种视觉上的层级结构;右缩进越深,它们在类中的嵌套就越深。
下面我们解释一下图中的变量。
- characterClass 变量声明在类的顶部,这意味着我们可以在 LeamingCurve类的任何位置通过名称对它进行引用。你可能听说过变量的可见性这一概念这是一种很好的思考方式
- characterHealth 变量声明在 Stat 方法内部,这意味着这个变量仅在 Start 方内部可见。我们仍然可以在 Stat 方法中访问 characterClass 变量,但是,如尝试从Stat 方法以外的任何位置访问 characterHealth 变量,则会报错。
- characterName 和 characterHealth 变量是一样的,前者只能在 CreateCharacta方法内部访问。这里只是为了说明类中可以有多个甚至嵌套的局部作用域。
如果经常与程序员打交道,就会听到关于变量的最佳声明位置的讨论或争论。答案比你想象的要简单: 声明变量时应牢记其用途。如果有需要在整个类中访问的变量就将其声明为类变量。如果仅在代码的特定部分需要变量,就将其声明为局部变量。
运算符
编程语言中的运算符表示类型可以执行的算术、赋值、关系和逻辑运算等。算运算符表示基本的数学运算,而赋值运算符则表示对给定的值可以同时执行数学和值运算。关系运算符和逻辑运算符用于计算多个值之间的条件,如大于、小于或等于此时,有必要先介绍算术运算符和赋值运算符,我们会在第4 章介绍关系运算符和辑运算符。
算术和赋值
算术运算符如下:
- +,表示加法
- -,表示减法
- /,表示除法
- *,表示乘法
C#运算符遵循常规的运算顺序,先计算括号内的值,再进行指数、乘法、除法加法和减法运算。
例如,以下算式即使包含相同的值和运算符,结果也会有所不同:
5+4-3/2*1=8
5+(4-3) /2*1=5
通过把算术和等号结合在一起使用,赋值运算符可用于任何数学运算的简写替换例如,如果想乘以某个变量,以下两种方式会产生相同的结果
int currentAge= 32;
currentAge = currentAge * 2;
另一种方式如下:
int currentAge = 32;
currentAge*= 2;
注意:
在 C#中,等号被视为赋值运算符。以下赋值运算符跟之前的示例遵循相同的语法:+-、=和/分别用于加法赋值、减法赋值、除法赋值。字符串对于运算荐来说是一种特殊情况,因为它们可使用加号来拼接文本,如下所示
string fullName ="Joe"+"Smith";
提示:
这种方法产生的代码往往十分笨拙,大多数情况下,字符串插值是拼接文本的首选方法。
实践一-执行错误的类型操作
我们已经学习了类型的一些规则,用于控制如何进行操作和交互,但还没实践过.下面尝试把浮点数和布尔值相加,就像我们之前执行的数字运算一样:
// Use this for initialization
void Start ()
{
Debug,Log(firstName + allGood);
}
刚刚发生了什么
控制台中会出现一条错误消息,指示我们不能把浮点数和布尔相加。当看到这种类型的错误时,请返回并检查变量类型是否兼容。
定义方法
前面一章,我们简要介绍了方法在程序中扮演的角色一 一存储和执行指令,就变量存储值一样。现在,我们需要理解方法的声明语法,以及它们如何在类中驱动作和行为。
基本语法
同变量一样,方法的声明也有一些基本要求:
- 需要返回数据类型。
- 必须具有以大写字母开头的唯一名称。
- 方法名的后面需要有一对括号。
- 需要使用一对花括号标记方法体(指令的存储位置)。
把以上所有这些要求放在一起,就可以得到如下简单的方法蓝图
returnType UniqueName ()
{
method body
}
下面分析 LearningCurve 脚本中默认的 Start 方法。
- Start 方法以 void 关键字开始,如果方法不返回任何数据,那么可以使用 void兰键字作为返回(数据)类型
- Start 方法有唯一的名称。
- 名称后有一对括号,用于保存任何可能的参数。
- 方法体由一对花括号定义。
1.修饰符和参数
与变量和输入参数一样,方法也有四个可用的访问修饰符。参数是变量占位符,可以传递到方法中并在方法内部访问。输入参数在数量上没有限制,但每个参数都要用逗号进行分隔,并且都有自己的数据类型和唯一的名称。
更新后的方法蓝图如下所示:
accessModifier returnType UniqueName (parameterType parameterName)
{
method body
}
提示:
如果没有显式的访问修饰符,则方法默认为私有的。与私有变量一样,利方法不能从其他脚本中调用。
为了调用方法(意味着运行或执行指今),我们需要输入方法名,后跟一对带或带参数的括号,并以分号结束:
// Without parameters
UniqueName();
// With parameters
UniqueName(parameterVariable);
实践一一定义一个简单的方法
我们曾一味地把AddNumbers 方法复制到 LearingCurve脚本中,而没有深入了解方法的细节。这一次,我们将有目的地创建方法。
// Use this for initialization
void Start ()
{
GenerateCharacter();
}
public void GenerateCharacter()
{
Debug,Log("Character: Spikd');
}
- 定义一个public 方法,返回类型为void,名称为GenerateCharacter
- 调用简单的 Debug.Log 方法,打印喜欢的游戏或电影中的角色名
- 在Start方法内部调用 GenerateCharacter 方法并单击 Play 按钮
刚刚发生了什么
当游戏启动时,Unity会自动调用Stat 方法,而Start 方法又会调用GenerateCharacter 方法并打印角色信息到控制台。
2.命名约定
与变量一样,方法也需要唯一的、有意义的名称以便在代码中区分它们。方法用来驱动操作,所以最好在命名时记住这一点。例如,GenerateCharacter 听起来像是命令,在脚本中调用时也很好理解,而像 Summary 这样的名称就很乏味,因为无法清晰地描绘出方法的作用。
方法名总是以大写字母开头,并且后续任何单词的首字母也需要大写。这种命名风格又称为帕斯卡大小写(PascalCase)。
3.方法是逻辑的绕行道
我们已经看到,代码行是按照编写的顺序执行的,但是引入方法会带来一种特殊情况。调用方法相当于告诉程序绕道进入方法的指令,逐个运行它们,然后在调用方法的地方按顺序继续执行。
看看自己是否能弄清楚调试日志会以什么顺序打印到控制台。
(1)程序将首先打印“Choose acharacter.”,因为这是代码的第一行。
(2)等到调用 GenerateCharacter 方法时,程序跳到第 23 行,打印 Character:Spike,然后回到第 17 行继续执行。
(3)在 GenerateCharacter 方法的所有代码运行结束后,打印“Afine choice.”
指定参数
所有方法不可能都像GenrateCharater 方法那样简单。为了传递额外的信息,们需要定义方法可以接收和使用的参数。方法的每个参数都是一条指令,需要满足以下要求:
- 拥有显式的类型。
- 拥有唯一的名称
是不是很熟悉?方法的参数在本质上是经过简化的变量声明,它们执行相同功能。每个参数的作用类似于局部变量,只能在特定方法的内部访问。
实参赋值
如果形参是方法可以接收的值的类型的蓝图,那么实参就是值本身。
- 传入方法的实参总是需要匹配形参的类型,就像变量的类型与值一样。
- 实参可以是字面值(如数字 2)或在类中其他位置声明的变量。
实践一添加方法参数
下面更改GenerateCharacter 方法以接收两个参数。
(I)添加两个参数:一个是用于角色名称的字符串类型,另一个是用于角色等级的整数类型
(2) 更改 Debug.Log 以使用新参数。
(3)使用自己的实参字面值或声明的变量)更改 Start 方法中的 GenerateCharacter方法调用。
// Use this for initialization
void Start ()
{
int characterLevel = 32;
GenerateCharacter("Spike",characterLevel);//实参
}
public void GenerateCharacter(string name,int level)//形参
{
Debug.LogFormat("Character: [0] - Level: (1],name, level);
}
刚刚发生了什么
name(字符串)和 level(整型),并在 GenerateCharacter 方我们定义了两个参数一法中像局部变量那样使用它们。当我们在 Start 方法中调用 GenerateCharacter方法时,我们为相应类型的每个形参添加了实参值。在引号中使用字符串与使用characterLevel 变量可以产生相同的结果,
指定返回值
除了接收参数,方法还可以返回任何 C#类型的值。前面的所有示例都使用了 void类型,这表示不返回任何数据,但方法的真正优点在于能够编写指令并返回计算结果。
根据方法的蓝图,方法的返回类型可在访问修饰符之后指定。除了类型之外,方法还需要包含 retumn 关键字和返回值。返回值可以是变量、字面值,甚至是表达式,只要与声明的返回类型匹配即可。
提示:返回类型为 void 的方法仍可以使用不带任何值或赋值表达式的 retumn 关键字。一旦到达带有 return 关键字的代码行,方法就会停止执行。这在想要避免某业行为或修止程序崩清的情况下非常有用.
1添加返回类型
下面更改GencrateCharacter 方法以返回整数
- 蒋方法声明中的返回类型从 void 改为int。
- 使用 rcum 关键字将返回值设置为 level +5,
public int GenerateCharacter(string name, int level)
{
Debug.LogFormat(“character: (0) - Level: (1y", name, level);
return level + 5;
}
刚刚发生了什么
现在,GenerateCharacter 方法会返回一个整数,这个整数是通过对 level 参数加!计算得到的。我们没有指定如何或是否使用这个返回值,这意味着脚本现在不会做何新的事情。
2.使用返回值
返回值的使用方式有以下两种:
- 创建局部变量来存储返回值
- 将调用方法本身用作返回值,从而像变量那样使用。调用方法是触发指令的实际代码行,GenerateCharacter("Spike",characterLevel)。如果需可以将调用方法作为参数传递给另一个方法。
3.实践捕获返回值
可通过两条简单的调试日志来捕获和使用返回值,
(1)创建一个名为 nextSkilLevel 的整型局部变量,并将已经准备好的GenerateCharacter 方法调用的返回值赋给这个变量。
(2)添加两条调试日志:第一条输出 nextSkilllevel: 第二条输出一个新的调用方法,参数值由你选择。
(3)用两个反斜杠(/注释掉 GenerateCharncter 方法中的调试日志,使控制台输出不那么杂乱。
(4)保存脚本并在 Unity 中单击 Pay 按钮。
// Use th1s for 1n1t1o11zation
vold Start ()
{
int characterLevel m 32:
int nextSkillLevel m GenerateCharacter("Spike",charactertLevel);
Debug.Log(nextSk111Level);
Debug.Log(GenerateCharacter("Faye",characterLevel)):
}
public int GenerateCharacter(string name, int level)
{
//Debug.LogPormat("Characteri (0) - Levet: (1)": hame, tevel);
return level + 5;
}
刚刚发生了什么
对于编译器来说,nextSkillLevel变量和 GenerateCharacter 方法调用都表示相同的信息,这就是两条日志均显示数字37的原因。
常见的Unity方法
现在,我们实际讨论 Uniy C#脚本中最常见的两个默认方法: Start Update与自定义方法不同,Unity引擎会根据它们各自的规则自动调用属于 MonoBeha类的方法。在大多数情况下,脚本中至少要有二个 MonoBehaviour 方法来启动自代码,这很重要。
Start方法
Uniy在启用脚本的第一调用的就是 Start 方法。MonoBehavior 脚本几乎总是附加到场景中的游戏对象上,当你单击 Play 按钮时,它们的附加脚本在加载的同时将被启用。在我们的项目中,LeamingCurve 脚本被附加到 Main Camera游戏对象这意味着当 Main Camera 被加载到场景中时,Start 方法就会运行。Stat 方法主要用在Update 方法运行之前第一次设置变量或者执行需要发生的逻辑。
注意:到目前为止,所有示例都使用了 Start 方法,即使它们没有执行设置操作但这通常不是使用 Start 方法的最佳方式。然而,Start 方法只需要触发一次这使其成为在控制台中显示一次性信息的绝佳工具。
Update方法
如果有足够多的时间查看参考脚本中的示例代码,就会注意到绝大多数代码是用 Update 方法执行的。。当游戏运行时,场景秒会刷新很多次,这被称为倾率或每传输帧数(Frames Per Second,FPS)。
在显示每一帧之后,Unity 会调用 Update 方法,Update 方法是游戏中执行次数多的方法之一,这使其非常适合用来检测鼠标和键盘输入或运行游戏逻辑。
如果对计算机的FPS 感到好奇,请在 Unity 中单击 Play 按钮,然后打开 Game视图中右上角的 Stats 选项卡。
总结
本章从编程的基本理论及构成要素快速过渡到了实际代码和C#语法层面。我们已经看到了代码格式的不同写法,学习了如何将信息调试到 Unity 控制台,并创建了第一个变量。对 C#类型、访问修饰符和变量作用域的介绍紧随其后,因为我们需要在Inspector面板中使用成员变量并开始涉足方法和操作。
方法有助于我们理解代码中的指令,但更重要的是掌握如何正确地运用它们的功能来实现一些有用的行为。输入参数、返回类型和方法签名都是重要的主题。你已经掌握了编程的两个基本模块,从现在开始,你要做的几乎所有事情就是对这两个基本模块进行扩展或应用。
在第4章,我们将介绍一种称为集合的 C#类型的特殊子集,集合可以用于存储组相关的数据,此外,我们还将介绍如何编写基于决策的代码。