自 C# 7.0 以来,语言中添加了许多有助于我们编写更少代码的代码改进。 本教程将重点介绍可以帮助我们编写更简洁易读的代码的六个新特性,以及我们如何在 C# for Unity 中使用这些特性。
这些是教程部分:
先决条件
设置我们的 Unity 项目
教程结构
Unity 中的 C# 功能支持
开关表达式
属性模式
类型模式
恒定模式
关系模式
逻辑模式
结论
遵循本教程需要以下先决条件:
Unity基础知识
以前在 Unity 中编写 C# 脚本的经验
首先,我们需要创建我们的 Unity 项目。 对于本教程,我们将使用 2021.3.4f1 版本,在我撰写本文时,它是最新的 LTS Unity 版本。
在项目模板列表中,选择 2D(core) (最简单的),为其命名,然后单击 创建项目 按钮。
项目启动后,创建一个名为 Scripts在 - 的里面 Assets文件夹。 在本教程中,我们将使用它们来组织我们的项目。
对于如何使用新 C# 功能的每个示例,我们将首先看看它之前是如何完成的,然后我们如何使用新功能编写越来越少可读性的代码。
下面的类只是在整个教程中用于所有示例的存根。 您可以将它们添加到脚本中 Scripts文件夹:
// GAME MODE. public enum GameMode { TimeAttack, Survive, Points } // ENEMIES. public abstract class Enemy { public bool IsVisible { get; set; } public bool HasArmor { get; set; } } public class Minion : Enemy { } public class Troll : Enemy { } public class Vampire : Enemy { } public class Zombie : Enemy { } // WEAPONS. public abstract class Weapon { } public class Stake : Weapon { } public class Shotgun : Weapon { }
在 C# 版本 8 和 9 中,该语言添加了许多新功能。 您可以在以下链接中阅读每个版本的完整功能列表:
C# 8.0 中的新功能
C# 9.0 中的新功能
Unity 对 C# 8 的支持 已从 2020.2 版本开始,C# 9 已从 2021.2 版本开始。
请注意,并非 Unity 支持所有 C# 8 和 9 功能,例如:
默认接口方法
指数和范围
异步流
异步一次性
抑制发出本地初始化标志
协变返回类型
模块初始化器
非托管函数指针的可扩展调用约定
仅初始化设置器
大多数这些不受支持的功能都用于非常特定的场景,例如 非托管函数指针的可扩展调用约定 ,而有些则不是,例如 索引和范围 。
因此,未来版本的 Unity 可能会支持索引和范围以及仅初始化设置器等功能。 但是,对于非常特定的场景,不受支持的功能在未来获得 Unity 支持的机会小于 索引和范围 等功能。
也许您可以找到一些解决方法来使用 Unity 中这些不受支持的功能,但我不鼓励您这样做,因为 Unity 是一个跨平台的游戏引擎。 新功能中的解决方法可能会导致您遇到难以理解、调试和解决的问题。
幸运的是,Unity 支持 C# 8 和 9 中的一些更常见的模式和表达式。让我们回顾一下下面一些最有用的模式和表达式,看看它们如何使我们能够编写更简洁的代码。
switch 表达式可以极大地简化和减少 LOC(代码行) ,以使 switch,因为我们可以避免一堆样板代码,比如 case 和 return 语句。
文档提示 :switch 表达式在表达式上下文中提供了类似 switch 的语义。 当开关臂产生一个值时,它提供了一种简洁的语法。
通常,switch 语句会在其每个 case 块中生成一个值。 Switch 表达式使您能够使用更简洁的表达式语法。 重复的 case 和 break 关键字更少,花括号也更少。
public string GetModeTitleOld(GameMode mode) { switch (mode) { case GameMode.Points: return "Points mode"; case GameMode.Survive: return "Survive mode"; case GameMode.TimeAttack: return "Time Attack mode"; default: return "Unsupported game mode"; } }
public string GetModeTitleNew(GameMode mode) { return mode switch { GameMode.Points => "Points mode", GameMode.Survive => "Survive mode", GameMode.TimeAttack => "Time Attack mode", _ => "Unsupported game mode", }; }
属性模式使您能够匹配检查的对象的属性 switch表达。
正如您在下面的示例中所见,使用属性模式,我们可以转换一系列 if语句转换为 switch 语句上的对象应匹配的简单属性列表。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
这 _ =>具有相同的含义 default在经典上 switch.
文档提示 :当表达式结果为非空且每个嵌套模式都匹配表达式结果的相应属性或字段时,属性模式匹配表达式。
public float CalculateDamageOld(Enemy enemy) { if (enemy.IsVisible) return enemy.HasArmor ? 1 : 2; return 0; }
public static float CalculateDamageNew(Enemy enemy) => enemy switch { { IsVisible: true, HasArmor: true } => 1, { IsVisible: true, HasArmor: false } => 2, _ => 0 };
我们可以使用类型模式来检查表达式的运行时类型是否与给定类型兼容。
类型模式与属性模式的逻辑几乎相同,但现在用于对象类型的上下文中。 我们可以变换一系列 if将对象类型检查到对象所在的类型列表中的语句 switch声明应该匹配。
public static float GetEnemyStrengthOld(Enemy enemy) { if (enemy is Minion) return 1; if (enemy is Troll) return 2; if (enemy is Vampire) return 3; if (enemy == null) throw new ArgumentNullException(nameof(enemy)); throw new ArgumentException("Unknown enemy", nameof(enemy)); }
public static float GetEnemyStrengthNew(Enemy enemy) => enemy switch { Minion => 1, Troll => 2, Vampire => 3, null => throw new ArgumentNullException(nameof(enemy)), _ => throw new ArgumentException("Unknown enemy", nameof(enemy)), };
使用类型模式,我们从 16 行代码减少到只有 8 行具有相同结果并且非常易于阅读和理解的代码。
常量模式可用于测试表达式结果是否等于指定的常量。
可能是最简单的模式匹配,它只匹配一个常量值——例如,一个字符串——然后返回结果。
public Enemy CreateEnemyByNameOld(string name) { if(name == null) throw new ArgumentNullException(nameof(name)); if (name.Equals("Minion")) return new Minion(); if (name.Equals("Troll")) return new Troll(); if (name.Equals("Vampire")) return new Vampire(); throw new ArgumentException($"Unknown enemy: {name}", nameof(name)); }
public Enemy CreateEnemyByNameNew(string name) => name switch { "Minion" => new Minion(), "Troll" => new Troll(), "Vampire" => new Vampire(), null => throw new ArgumentNullException(nameof(name)), _ => throw new ArgumentException($"Unknown enemy: {name}", nameof(name)), };
常量模式可以与任何常量表达式一起使用,例如 int, float, char, string, bool, 和 enum.
不要错过 The Replay 来自 LogRocket 的精选时事通讯
了解 LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
使用 React 的 useEffect 优化应用程序的性能
之间切换 在多个 Node 版本
了解如何 使用 AnimXYZ 为您的 React 应用程序制作动画
探索 Tauri ,一个用于构建二进制文件的新框架
比较 NestJS 与 Express.js
关系模式
关系模式会将表达式结果与常数进行比较。
这可能看起来是最复杂的模式匹配,但它的核心并没有那么复杂。 我们可以用关系模式做的是直接使用逻辑运算符作为 <, >, <=, 或者 >=评估对象,然后为 switch.
文档提示 :关系模式的右侧部分必须是常量表达式。
public string GetEnemyEnergyMessageOld(float energy) { if(energy < 0 || energy > 1) throw new ArgumentException("Energy should be between 0.0 and 1.0", nameof(energy)); if (energy >= 1f) return "Healthy"; if (energy > .5f) return "Injured"; return "Very hurt"; }
public string GetEnemyEnergyMessageNew(float energy) => energy switch { < 0 or > 1 => throw new ArgumentException("Energy should be between 0.0 and 1.0", nameof(energy)), >= 1 => "Healthy", > .5f => "Injured", _ => "Very hurt" };
任何 关系运算符 <, >, <=, 或者 >=可用于关系模式。
我们可以使用 not, and, 和 or模式组合器来创建逻辑表达式。
这就像关系模式的扩展,您可以在其中组合逻辑运算符 not, and, 和 or创建更复杂和精细的模式匹配。
文档提示 :您使用 not, and, 和 or模式组合器来创建以下逻辑模式:
否定 not当否定模式与表达式不匹配时匹配表达式的模式
连词 and当两个模式都匹配表达式时匹配表达式的模式
析取的 or当任一模式与表达式匹配时匹配表达式的模式
public float CalculateEnergyLossByStakeOld(Enemy enemy) { if (enemy == null) throw new ArgumentNullException(nameof(enemy)); if (enemy is not Vampire) return .1f; return 1f; }
public float CalculateEnergyLossByStakeNew(Enemy enemy) => enemy switch { null => throw new ArgumentNullException(nameof(enemy)), not Vampire => .1f, _ => 1f };
在本教程中,我们学习了如何使用 switch 表达式、属性模式、类型模式、常量模式、关系模式和逻辑模式在 Unity 上编写越来越现代的 C# 代码。
希望您可以在下一个项目中使用其中的一些,以便在编写更简洁的代码时节省自己的时间。
LogRocket 是一个前端应用程序监控解决方案,可让您重现问题,就好像它们发生在您自己的浏览器中一样。 无需猜测错误发生的原因,或要求用户提供屏幕截图和日志转储,LogRocket 可让您重播会话以快速了解问题所在。 无论框架如何,它都可以完美地与任何应用程序配合使用,并且具有用于记录来自 Redux、Vuex 和 @ngrx/store 的附加上下文的插件。
除了记录 Redux 操作和状态之外,LogRocket 还记录控制台日志、JavaScript 错误、堆栈跟踪、带有标头 + 正文的网络请求/响应、浏览器元数据和自定义日志。 它还检测 DOM 以记录页面上的 HTML 和 CSS,即使是最复杂的单页和移动应用程序也能重新创建像素完美的视频。