C#基础语言开发 1.基础准备 1.1 准备工作 确保已经安装以下环境,即可在VS Code中正常运行项目
1.2 创建新项目 方法一:使用终端命令
1 2 3 4 5 6 7 mkdir MyFirstCSharpProjectcd MyFirstCSharpProjectdotnet new console
方法二:使用 VS Code 命令面板
按 Ctrl+Shift+P
打开命令面板
输入 .NET: New Project
选择 “Console Application”
选择项目位置和名称
1.3 项目结构 创建成功后,你会看到以下文件结构:
1 2 3 4 MyFirstCSharpProject/ ├── Program.cs # 主程序文件 ├── MyFirstCSharpProject.csproj # 项目配置文件 └── obj/ # 编译生成文件
1.4 配置和运行 1.4.1 打开项目
1.4.2 理解 Program.cs 默认生成的 Program.cs
内容:
1 2 Console.WriteLine("Hello, World!" );
1.4.3 运行项目 方法一:使用 .NET CLI(推荐)
1 2 3 4 5 6 dotnet run dotnet build dotnet run
方法二:使用 Code Runner 扩展
安装 “Code Runner” 扩展
打开 Program.cs
文件
点击右上角的 ▶️ 运行按钮
或按 Ctrl+Alt+N
方法三:使用调试功能
按 F5
或点击运行 → 启动调试
首次运行会创建 .vscode/launch.json
配置文件
1.5 📝 完整示例:创建自定义项目 让我们创建一个更完整的示例:
1.5.1 创建项目 1 2 3 dotnet new console -n CalculatorApp cd CalculatorAppcode .
1.5.2 修改 Program.cs 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 using System;namespace CalculatorApp { class Program { static void Main (string [] args ) { Console.WriteLine("=== 简易计算器 ===" ); while (true ) { Console.WriteLine("\n请选择操作:" ); Console.WriteLine("1. 加法" ); Console.WriteLine("2. 减法" ); Console.WriteLine("3. 退出" ); Console.Write("请输入选择 (1-3): " ); string choice = Console.ReadLine(); if (choice == "3" ) { Console.WriteLine("感谢使用!" ); break ; } if (choice == "1" || choice == "2" ) { Console.Write("请输入第一个数字: " ); double num1 = Convert.ToDouble(Console.ReadLine()); Console.Write("请输入第二个数字: " ); double num2 = Convert.ToDouble(Console.ReadLine()); double result = choice == "1" ? num1 + num2 : num1 - num2; string operation = choice == "1" ? "+" : "-" ; Console.WriteLine($"结果: {num1} {operation} {num2} = {result} " ); } else { Console.WriteLine("无效选择,请重新输入!" ); } } } } }
1.5.3 运行项目
1.6 ⚙️ 重要配置 1.6.1 调试配置 如果调试功能不正常,检查 .vscode/launch.json
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "version" : "0.2.0" , "configurations" : [ { "name" : "C# Demo Debug" , "type" : "coreclr" , "request" : "launch" , "preLaunchTask" : "build" , "program" : "${workspaceFolder}/bin/Debug/net8.0/C#.dll" , "args" : [ ] , "cwd" : "${workspaceFolder}" , "console" : "internalConsole" , "stopAtEntry" : false } ] }
1.6.2 任务配置(可选) 可选的任务配置:.vscode/tasks.json 一般launch.json中配置了preLaunchTask 时使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "version" : "2.0.0" , "tasks" : [ { "label" : "build" , "command" : "dotnet" , "type" : "process" , "args" : [ "build" , "${workspaceFolder}/C#.csproj" , "/property:GenerateFullPaths=true" , "/consoleloggerparameters:NoSummary" ] , "group" : "build" , "presentation" : { "reveal" : "silent" } , "problemMatcher" : "$msCompile" } ] }
2.数据类型
类别
子类别
具体类型
说明
值类型
整数类型
sbyte
, byte
, short
, ushort
8位和16位整数,有符号和无符号
int
, uint
, long
, ulong
32位和64位整数,有符号和无符号
浮点类型
float
, double
, decimal
单精度、双精度和高精度小数
字符类型
char
16位Unicode字符
布尔类型
bool
布尔值,true或false
结构体
struct
自定义值类型
枚举
enum
命名常量集合
引用类型
类
object
, string
, 自定义class
所有类型的基类、字符串、自定义类
接口
interface
定义契约接口
数组
int[]
, string[]
等
相同类型元素的集合
委托
delegate
方法引用类型
特殊类型
动态类型
dynamic
运行时类型解析
可空类型
int?
, bool?
等
允许值类型为null
补充说明:
值类型:直接存储数据,分配在栈上,赋值时复制完整值。
引用类型:存储数据的内存地址,分配在堆上,赋值时复制引用。
默认值:值类型默认为 0/false 等,引用类型默认为 null。
示例代码:
1 2 3 4 int number = 10 ; string text = "Hello" ; int ? nullableInt = null ; dynamic dynamicVar = "Dynamic" ;
3.C# 与 TypeScript 对比学习指南(Godot开发重点) 3.1 基础数据类型与语法对比 3.1.1 基础数据类型
TypeScript
C# (Godot)
说明
let num: number = 10;
int num = 10;
整数类型
let str: string = "hello";
string str = "hello";
字符串
let bool: boolean = true;
bool flag = true;
布尔值
let arr: number[] = [1,2,3];
int[] arr = new int[] {1,2,3};
数组
let map = new Map<string, number>();
Dictionary<string, int> dict = new();
字典/Map
let set = new Set<number>();
HashSet<int> set = new();
集合
let obj: any = "anything";
dynamic obj = "anything";
动态类型
3.1.2 变量声明与访问修饰符 TypeScript:
1 2 3 4 5 6 class Example { public name : string = "public" ; private age : number = 20 ; protected email?: string ; readonly id : number = 1 ; }
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Example { public string Name { get ; set ; } = "public" ; private int _age = 20 ; protected string ? Email { get ; set ; } public readonly int Id = 1 ; public int Score { get ; private set ; } private int _health = 100 ; public int Health { get => _health; set { _health = Math.Clamp(value , 0 , 100 ); OnHealthChanged?.Invoke(_health); } } }
3.1.3 类与继承 TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Entity { constructor (public id: number ) {} move (): void { console .log ("Moving" ); } } class Player extends Entity { private health : number = 100 ; takeDamage (damage : number ): void { this .health -= damage; } }
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Entity { public int Id { get ; } public Entity (int id ) { Id = id; } public virtual void Move () { GD.Print("Moving" ); } } public class Player : Entity { private int _health = 100 ; public Player (int id ) : base (id ) { } public void TakeDamage (int damage ) { _health -= damage; } public override void Move () { base .Move(); GD.Print("Player is moving" ); } }
3.1.4 接口与枚举 TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 enum Direction { Up = "UP" , Down = "DOWN" } interface IDamageable { health : number ; takeDamage (amount : number ): void ; } class Enemy implements IDamageable { health : number = 100 ; takeDamage (amount : number ): void { this .health -= amount; } }
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public enum Direction{ Up, Down, Left, Right } public interface IDamageable { int Health { get ; } void TakeDamage (int amount ) ; } public class Enemy : IDamageable { public int Health { get ; private set ; } = 100 ; public void TakeDamage (int amount ) { Health -= amount; } }
3.2 C# 特有重要特性 3.2.1 委托 (Delegates) - C#的核心特性 委托类似于TypeScript中的函数类型,但更强大:
基本委托:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public delegate void SimpleDelegate () ;public delegate int CalculateDelegate (int a, int b ) ;public class Calculator { public void TestDelegates () { SimpleDelegate del1 = SayHello; CalculateDelegate del2 = Add; del1(); int result = del2(5 , 3 ); SimpleDelegate multiDel = SayHello; multiDel += SayGoodbye; multiDel(); } private void SayHello () => GD.Print("Hello!" ); private void SayGoodbye () => GD.Print("Goodbye!" ); private int Add (int a, int b ) => a + b; }
在Godot中的实际应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public partial class EventManager : Node { public delegate void GameEventDelegate (string eventName, object data ) ; public static GameEventDelegate OnGameEvent; public static void TriggerEvent (string eventName, object data = null ) { OnGameEvent?.Invoke(eventName, data); } } public partial class UI : Control { public override void _Ready() { EventManager.OnGameEvent += HandleGameEvent; } private void HandleGameEvent (string eventName, object data ) { switch (eventName) { case "PlayerDamaged" : UpdateHealthBar(data); break ; case "LevelComplete" : ShowLevelComplete(); break ; } } }
3.2.2 事件 (Events) - 封装后的委托 事件提供了更好的封装性和安全性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public class Player : CharacterBody2D { public event Action<int > HealthChanged; public event Action PlayerDied; public event Action<string , int > ItemCollected; private int _health = 100 ; public void TakeDamage (int damage ) { _health -= damage; HealthChanged?.Invoke(_health); if (_health <= 0 ) { PlayerDied?.Invoke(); } } public void CollectItem (string itemName, int value ) { ItemCollected?.Invoke(itemName, value ); } } public partial class GameUI : Control { private Player _player; public override void _Ready() { _player = GetNode<Player>("Player" ); _player.HealthChanged += OnHealthChanged; _player.PlayerDied += OnPlayerDied; _player.ItemCollected += OnItemCollected; } private void OnHealthChanged (int newHealth ) { GetNode<Label>("HealthLabel" ).Text = $"Health: {newHealth} " ; } private void OnPlayerDied () { ShowGameOverScreen(); } private void OnItemCollected (string itemName, int value ) { ShowItemPopup(itemName); } }
3.2.3 泛型 (Generics) 比TypeScript的泛型更强大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class ObjectPool <T > where T : Node , new (){ private Queue<T> _pool = new Queue<T>(); public T GetObject () { if (_pool.Count > 0 ) return _pool.Dequeue(); return new T(); } public void ReturnObject (T obj ) { _pool.Enqueue(obj); } } public class GameUtils { public static T FindComponentInChildren <T >(Node parent ) where T : class { foreach (Node child in parent.GetChildren()) { if (child is T component) return component; T found = FindComponentInChildren<T>(child); if (found != null ) return found; } return null ; } } var bulletPool = new ObjectPool<Bullet>();Bullet bullet = bulletPool.GetObject(); Player player = GameUtils.FindComponentInChildren<Player>(this );
3.2.4 特性 (Attributes) - Godot中大量使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public partial class Player : CharacterBody2D { [Export ] public int MaxHealth { get ; set ; } = 100 ; [Export(PropertyHint.Range, "0,100,1" ) ] public int Health { get ; set ; } = 100 ; [Export(PropertyHint.File, "*.tscn" ) ] public string LevelScenePath { get ; set ; } [ExportGroup("Combat Settings" ) ] [Export ] public int AttackDamage = 10 ; [Signal ] public delegate void HealthChangedEventHandler (int newHealth ) ; [Export ] private NodePath _spritePath; private Sprite2D _sprite; public override void _Ready() { _sprite = GetNode<Sprite2D>(_spritePath); } }
3.3 命名空间与Godot的特殊性 3.3.1 命名空间使用 传统C#项目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 namespace MyGame.Core { public class GameManager { } } namespace MyGame.UI { using MyGame.Core; public class GameUI { private GameManager _manager; } }
Godot中的常见模式:
1 2 3 4 5 6 7 8 9 10 11 using Godot;using System.Collections.Generic;public partial class Player : CharacterBody2D { }
3.3.2 Godot中的using指令 1 2 3 4 5 6 7 8 9 10 using Godot; using Godot.Collections; using System; using System.Linq; using System.Threading.Tasks; public partial class AdvancedExample : Node { }
3.4 Godot中的设计模式与消息传递 3.4.1 观察者模式 - Godot信号系统 Godot信号(推荐):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public partial class Player : CharacterBody2D { [Signal ] public delegate void HealthChangedEventHandler (int newHealth ) ; [Signal ] public delegate void PlayerDiedEventHandler () ; private int _health = 100 ; public void TakeDamage (int damage ) { _health -= damage; EmitSignal(SignalName.HealthChanged, _health); if (_health <= 0 ) EmitSignal(SignalName.PlayerDied); } } public partial class UI : Control { public override void _Ready() { Player player = GetNode<Player>("../Player" ); player.HealthChanged += OnHealthChanged; player.PlayerDied += OnPlayerDied; } private void OnHealthChanged (int newHealth ) { } private void OnPlayerDied () { } }
3.4.2 单例模式 - Godot自动加载 创建全局单例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public partial class GameManager : Node { public static GameManager Instance { get ; private set ; } public int Score { get ; private set ; } public Player CurrentPlayer { get ; set ; } public override void _Ready() { if (Instance == null ) { Instance = this ; } else { QueueFree(); } } public void AddScore (int points ) { Score += points; } } public partial class Coin : Area2D { public override void _Ready() { BodyEntered += OnBodyEntered; } private void OnBodyEntered (Node body ) { if (body is Player) { GameManager.Instance.AddScore(10 ); QueueFree(); } } }
3.4.3 事件总线模式 - 全局消息系统 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public static class EventBus { public static event Action<string > OnMessage; public static event Action<int > OnScoreChanged; public static event Action<Player> OnPlayerSpawned; public static void PublishMessage (string message ) { OnMessage?.Invoke(message); } public static void PublishScoreChanged (int newScore ) { OnScoreChanged?.Invoke(newScore); } public static void PublishPlayerSpawned (Player player ) { OnPlayerSpawned?.Invoke(player); } } public partial class Enemy : CharacterBody2D { private void Die () { EventBus.PublishMessage("Enemy defeated!" ); EventBus.PublishScoreChanged(100 ); } } public partial class UI : Control { public override void _Ready() { EventBus.OnMessage += ShowMessage; EventBus.OnScoreChanged += UpdateScoreDisplay; } private void ShowMessage (string message ) { GetNode<Label>("MessageLabel" ).Text = message; } private void UpdateScoreDisplay (int score ) { GetNode<Label>("ScoreLabel" ).Text = $"Score: {score} " ; } }
3.4.4 服务定位器模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public static class ServiceLocator { private static Dictionary<Type, object > _services = new Dictionary<Type, object >(); public static void Register <T >(T service ) { _services[typeof (T)] = service; } public static T Get <T >() { if (_services.TryGetValue(typeof (T), out object service)) { return (T)service; } throw new Exception($"Service {typeof (T)} not registered" ); } public static bool TryGet <T >(out T service ) { if (_services.TryGetValue(typeof (T), out object obj)) { service = (T)obj; return true ; } service = default ; return false ; } } public partial class AudioManager : Node { public override void _Ready() { ServiceLocator.Register<AudioManager>(this ); } public void PlaySound (string soundName ) { } } public partial class Player : CharacterBody2D { public void TakeDamage () { if (ServiceLocator.TryGet<AudioManager>(out AudioManager audio)) { audio.PlaySound("hurt" ); } } }
3.5 异步编程对比 3.5.1 Promise vs async/await TypeScript:
1 2 3 4 5 6 7 8 9 10 function loadData ( ): Promise <string > { return new Promise ((resolve ) => { setTimeout (() => resolve ("Data loaded" ), 1000 ); }); } async function initialize ( ): Promise <void > { const data = await loadData (); console .log (data); }
C#:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public async Task<string > LoadDataAsync (){ await Task.Delay(1000 ); return "Data loaded" ; } public async Task InitializeAsync (){ string data = await LoadDataAsync(); GD.Print(data); } public async Task LoadSceneAsync (string scenePath ){ var sceneLoader = ResourceLoader.LoadThreadedRequest(scenePath); while (ResourceLoader.LoadThreadedGetStatus(scenePath) == ResourceLoader.ThreadLoadStatus.InProgress) { await ToSignal(GetTree(), "process_frame" ); } PackedScene scene = ResourceLoader.LoadThreadedGet(scenePath) as PackedScene; AddChild(scene.Instantiate()); }