本部分将系统性地介绍 C# 编程语言,帮助读者从零基础逐步掌握其基本用法与核心概念。课程内容涵盖开发环境的搭建、首个项目的创建、基础语法的讲解及典型用例示范。 无论有无编程经验,你都可以通过循序渐进的学习,理解每个关键点,真正掌握 C# 项目开发的流程与方法。

先说几个关键词:现代、强类型、面向对象、跨平台。C# 由微软设计,和 .NET 平台一起工作。今天的 C# 可以在 Windows、macOS、Linux 上运行; 可以写控制台程序、桌面软件、Web 后端、小游戏,甚至移动应用(Xamarin/Maui)。
你会经常看到两个名字:C# 和 .NET。简单理解:C# 是语言,就像“中文”; .NET 是运行时和标准库,就像“字典+工具箱”。我们用 C# 说话,用 .NET 提供的工具去做事。
要写 C#,我们需要两个东西:
安装完成后,打开终端(Windows 上可以用 PowerShell),输入:
|dotnet --version
如果能看到一个版本号,比如 8.x 或 7.x,说明 .NET 安装成功。
接着,我们来创建第一个控制台项目。选择一个空文件夹,在终端执行:
|dotnet new console -n HelloCSharp cd HelloCSharp dotnet run
你应该会看到屏幕上打印出:
|Hello, World!
我们已经把“第一个音”发出来了。下面,我们不急着往下跑,先把这个小小的“Hello, World!”拆开看看。
创建出来的项目里,有一个文件 Program.cs。它的内容大概是这样(不同版本模板略有差别):
|// Program.cs using System; // 引入 System 命名空间(提供控制台、基础类型等常用功能) // 这是主程序入口点。对顶级语句模板而言,编译器会为我们生成 Main 方法。 Console.WriteLine("Hello, World!"); // 输出一行文本到控制台
如果你看到的是带 namespace 和 class Program 的版本,也没关系,原理一样。新模板为了简洁,省略了那些“礼仪性的开场白”,让我们可以专注在核心逻辑上。
我们后面会讲“命名空间”“类”和“Main 方法”到底是什么。先记住两点:
第一,Console.WriteLine(...) 是输出一行文字;第二,代码一行行从上往下执行,遇到输出就把内容打印到屏幕上。
很多书一上来就把“程序结构”讲得很复杂,我们只抓关键:
using System; 就是在说:我要用 System 里的东西。Main。顶级语句模板把它藏起来了,但它仍然存在。为了让你看到“完整版”,我们写一个带 Main 的最小示例:
|// Program.cs(显式 Main 版本) using System; // 引入基础工具箱 namespace GettingStarted // 命名空间,像文件夹名 { internal class Program // 定义一个类 Program,internal 先不用纠结 { // Main 是程序入口。string[] args 是命令行参数(暂时不用) static void Main(string[] args) { Console.WriteLine("你好,C# 的世界!"); } } }
逐行理解:我们先搬进 System 工具箱,然后声明一个命名空间 GettingStarted,里面放一个 Program 类。类里有一个 Main 方法,程序从这里开始,执行输出语句,结束。
程序和现实一样,需要“记住东西”。变量就是“带名字的盒子”,数据类型规定了盒子里能放什么:整数、浮点数、文本、真假等等。
常见基础类型:
int(32位整数)、long(更大)double(常用浮点)、float(更小精度)、decimal(高精度常用于金额)string(字符串)、char(单个字符)bool(true 或 false)我们写一个小程序:记录今天学习的时长,然后打印出来。
|// 变量示例:study-time.cs using System; class StudyTimeDemo { static void Main() { // 声明一个整型变量,表示今天学习了多少小时 int hours = 2; // 我们先赋值 2,后面可以改 // 声明一个浮点型变量,记录分钟(小数) double minutes = 30.5; // 30.5 分钟 // 声明一个字符串变量,记录学习主题 string topic = "C# 入门"; // 声明一个布尔变量,记录今天是否达成目标 bool goalReached = true; // 输出这些信息 Console.WriteLine("今天学习主题: " + topic); Console.WriteLine("学习时长(小时): " + hours); Console.WriteLine("学习时长(分钟): " + minutes); Console.WriteLine("是否达成目标: " + goalReached); } }
运行后你会看到相应的输出。注意 + 在拼接字符串时的作用:把多个片段连成一段文本。也可以用插值字符串的写法,更优雅:
|Console.WriteLine($"今天学习主题: {topic}");
插值字符串里,花括号 {} 就像“窗口”,可以把变量的值展示出来。
变量名请用有意义的英文或拼音英文化,推荐使用小驼峰:studyHours、targetReached。我们要写给“未来的自己”和“团队伙伴”看,越清晰越好。
有时候需要把一种类型变成另一种,比如把字符串变成整数,或者让整数参与小数计算。最常见的是这几种方式:
|int a = 5; double b = a; // 隐式转换:int 可以自动转 double double c = 3.14; int d = (int)c; // 显式转换(强制类型转换):小数到整数需要手动,可能丢失小数部分 string s = "123"; int num = int.Parse(s); // 解析字符串为整数(若 s 非法会抛异常) // 更安全:TryParse,不抛异常,用返回值表示是否成功 bool ok = int.TryParse(s, out int value);
TryParse 像是“温柔型转换器”,失败也不生气,返回 false,并把输出变量设为默认值。
Console.WriteLine 是“我们说话”;Console.ReadLine 是“我们倾听”。我们做一个小例子:计算 BMI(体质指数),并且把每一步解释清楚。
|// bmi.cs using System; class BmiCalculator { static void Main() { // 提示并读取身高(米) Console.Write("请输入你的身高(米): "); string? heightInput = Console.ReadLine(); // 可能为 null,用 ? 表示可空 // 提示并读取体重(千克) Console.Write("请输入你的体重(kg): "); string? weightInput = Console.ReadLine(); // 尝试把输入解析为 double bool hOk = double.TryParse(heightInput, out double height); bool wOk = double.TryParse(weightInput, out double weight); if (!hOk || !wOk || height <= 0 || weight <= 0) { Console.WriteLine("输入不合法,请输入正数,例如 身高: 1.72,体重: 65.5"); return; // 早退出,避免继续计算 } // 计算 BMI = 体重 / 身高^2 double bmi = weight / (height * height); Console.WriteLine($"你的 BMI: {bmi:F2}"); // :F2 保留两位小数 // 简单分类 if (bmi < 18.5) { Console.WriteLine("偏瘦,注意营养均衡和力量训练"); } else if (bmi < 24) { Console.WriteLine("正常,保持良好作息和运动"); } else if (bmi < 28) { Console.WriteLine("偏重,建议控制饮食+有氧运动"); } else { Console.WriteLine("偏高,建议咨询专业人士制定计划"); } } }
这个程序用到了输入、解析、条件判断、格式化输出。我们也见识了“早返回”的写法:当输入不对,直接 return,让代码更干净。
C# 提供了丰富的运算符来处理数据和逻辑:
算术运算符:
+(加法)、-(减法)、*(乘法)、/(除法)、%(取余)5 / 2 结果是 2,而 5.0 / 2 结果是 2.5n % 2 == 0 表示偶数比较运算符:
==(等于)、!=(不等于)<(小于)、<=(小于等于)、>(大于)、>=(大于等于)bool 类型的结果逻辑运算符:
&&(逻辑与,短路求值)、||(逻辑或,短路求值)、!(逻辑非)a && b 当 a 为 false 时不会计算 b;a || b 当 a 为 true 时不会计算 b运算符优先级:算术 > 比较 > 逻辑,但复杂表达式建议用括号明确意图,让代码更易读。比如 (age >= 18) && (score > 60) 比 age >= 18 && score > 60 更清晰。
我们写一个“目标检查器”:
|// goal-checker.cs using System; class GoalChecker { static void Main() { int dailyTarget = 60; // 每天目标学习 60 分钟 Console.Write("请输入今天学习分钟数: "); string? input = Console.ReadLine(); bool ok = int.TryParse(input, out int minutes); if (!ok || minutes < 0) { Console.WriteLine("请输入非负整数,如 45 或 120。"); return; } bool reached = minutes >= dailyTarget; // 比较运算 int diff = minutes - dailyTarget; // 算术运算 Console.WriteLine($"是否达成目标: {reached}"); Console.WriteLine(diff >= 0 ? $"超出目标 {diff} 分钟,干得漂亮!" : $"还差 {-diff} 分钟,明天加油!"); } }
注意三元表达式 条件 ? 值1 : 值2。虽然方便,但不要滥用,复杂情况用 if/else 更清晰。
程序需要根据不同情况做出不同的行为,这就是"分支控制"。就像我们在现实生活中做决定一样:如果天气晴朗就去公园,如果下雨就在家看书。
在 C# 中,最常用的分支结构是 if/else,它让程序能够"看情况行事"。当我们需要处理多个条件时,switch 语句(特别是 C# 8+ 的 switch 表达式)能让代码更加简洁清晰。
下面我们写一个成绩评级器,同时演示 if/else 和 switch 两种写法的区别:
|// grade.cs using System; class GradeClassifier { static void Main() { Console.Write("请输入成绩(0-100): "); string? input = Console.ReadLine(); // 检查输入是否合法 if (!int.TryParse(input, out int score) || score < 0 || score > 100) { Console.WriteLine("请输入 0 到 100 之间的整数"); return; } // if/else 版本:处理多个条件 string level; if (score >= 90) level = "A"; else if (score >= 80) level = "B"; else if (score >= 70) level = "C"; else if (score >= 60) level = "D"; else level = "E"; Console.WriteLine($"评级(If/Else): {level}"); // switch 版本(C# 8+ 支持 switch 表达式) string level2 = score switch { >= 90 => "A", >= 80 => "B", >= 70 => "C", >= 60 => "D", _ => "E" // 下划线表示其它情况 }; Console.WriteLine($"评级(Switch): {level2}"); } }
switch 表达式常常让代码更紧凑。记住:清晰第一,紧凑第二。
当我们需要重复做某件事,编程语言提供了四种主要的循环方式:
我们通过一个"猜数字游戏"来实际体验这几种循环。这个游戏的规则是:程序想一个数字,用户有限次机会猜,每次猜错会提示"大了"或"小了",猜对了就胜利。
|// guess.cs using System; class GuessNumber { static void Main() { // 目标数字(真实项目里我们会用随机数,这里为了讲解固定为 42) int secret = 42; // 最多尝试 5 次 int maxTries = 5; // for 循环控制次数 for (int attempt = 1; attempt <= maxTries; attempt++) { Console.Write($"第 {attempt}/{maxTries} 次,请猜一个 1-100 的数: "); string? input = Console.ReadLine(); if (!int.TryParse(input, out int guess)) { Console.WriteLine("请输入整数"); attempt--; // 不计入无效输入 continue; // 继续下一轮 } if (guess == secret) { Console.WriteLine("猜对了!"); break; // 提前结束循环 } else if (guess < secret) { Console.WriteLine("小了,再试试"); } else { Console.WriteLine("大了,再试试"); } if (attempt == maxTries) { Console.WriteLine($"机会用完,正确答案是 {secret}"); } } // while 示例:从 5 倒数到 1 int n = 5; while (n > 0) { Console.WriteLine(n); n--; // 别忘记更新条件,否则会成“死循环” } // do...while:至少执行一次 int x = 0; do { Console.WriteLine($"do...while 第一次执行: x = {x}"); x++; } while (x < 0); // 条件为假,但循环体仍执行了一次 // foreach:遍历集合 int[] scores = { 95, 82, 76 }; foreach (int s in scores) { Console.WriteLine($"分数: {s}"); } } }
循环的关键是“循环条件”和“更新状态”。当你调试循环问题时,多打印变量,像打手电筒一样照亮黑箱。
数组就像是固定座位数的教室——你提前订好 10 个位置,就只能坐 10 个人,不能临时加椅子。声明时必须指定长度:int[] scores = new int[5];,这就预留了 5 个整数的空间。
相比之下,List<T> 更像是可以随时扩容的自助餐厅——开始可能只摆 3 张桌子,人多了就加桌子,人少了也能收起来。我们可以用 Add() 添加元素,用 Remove() 删除元素,用 Count 属性查看当前有多少项。
两者各有适用场景:如果你明确知道要存储的数据个数(比如一周 7 天的气温),数组足够简单高效;如果数据量会动态变化(比如用户逐个输入的购物清单),List<T> 更灵活方便。
下面我们用它们做一个小型"成绩统计器",看看实际使用中的差别:
|// scores.cs using System; using System.Collections.Generic; // 使用 List 所需的命名空间 class ScoreStats { static void Main() { // 用数组存三次测验的成绩 int[] exams = new int[3]; exams[0] = 85; exams[1] = 92; exams[2] = 78; // 用 List 存可变数量的作业成绩 List<int> homeworks = new List<int> { 100, 90, 95, 80 }; // 计算数组平均分 double examAvg = Average(exams); Console.WriteLine($"测验平均分: {examAvg:F1}"); // 计算列表平均分 double hwAvg = Average(homeworks); Console.WriteLine($"作业平均分: {hwAvg:F1}"); // 合并后整体平均 List<int> all = new List<int>(); all.AddRange(exams); all.AddRange(homeworks); Console.WriteLine($"总体平均分: {Average(all):F1}"); } // 方法重载:支持数组 static double Average(int[] numbers) { if (numbers == null || numbers.Length == 0) return 0; int sum = 0; foreach (int n in numbers) sum += n; return (double)sum / numbers.Length; } // 方法重载:支持 List static double Average(List<int> numbers) { if (numbers == null || numbers.Count == 0) return 0; int sum = 0; foreach (int n in numbers) sum += n; return (double)sum / numbers.Count; } }
我们刚才见到了"方法重载"的实际应用:两个同名的 Average 方法,但参数类型不同——一个接受 int[] 数组,另一个接受 List<int> 列表。
这是 C# 的一个强大特性,让我们能用同一个方法名表达相同的逻辑概念,而编译器会根据我们传入的参数类型自动选择合适的版本。
想象一下,如果没有方法重载,我们可能需要写成 AverageArray() 和 AverageList() 这样的名字。
但"求平均数"本质上是同一个动作,不管容器是什么类型。方法重载让代码更简洁、更符合人类思维:我们说"计算平均值",系统自动判断用哪种实现方式。
注释是给“未来的我们”看的。好的注释解释“为什么”,而不是“怎么写”。
// 标注关键意图。示例:
|// 计算应付款:在原价基础上应用折扣,然后加上税费 static decimal CalculatePayable(decimal price, decimal discountRate, decimal taxRate) { decimal discounted = price * (1 - discountRate); decimal taxed = discounted * (1 + taxRate); return taxed; }
即便没有中文注释,也能从命名看出意图。这就是“写给未来的我们”。
当你 dotnet new console 时,会生成一个 .csproj 文件。它记录了项目名称、目标框架、依赖等信息。一个典型的控制台项目结构:
|HelloCSharp/ HelloCSharp.csproj Program.cs
常用命令:
|dotnet restore # 还原依赖(首次/变更依赖时) dotnet build # 构建项目(编译) dotnet run # 运行(会自动构建) dotnet publish # 发布可部署的产物
9. 温度转换器练习
编写一个温度转换程序,实现以下功能:
Console.Write() 或 Console.WriteLine() 提示用户输入摄氏度Console.ReadLine() 读取用户输入的字符串double.Parse() 或 Convert.ToDouble() 将字符串转换为浮点数F = C × 9/5 + 32 计算华氏度Console.WriteLine() 输出结果,格式为 $"{fahrenheit:F1}°F",保留1位小数try-catch 处理(可选)|using System; class TemperatureConverter { static void Main() { Console.Write("请输入摄氏度: "); string input = Console.ReadLine(); double celsius = double.Parse(input); double fahrenheit = celsius * 9.0 / 5.0 + 32; Console.WriteLine($"{fahrenheit:F1}°F"); } }
|请输入摄氏度: 25 77.0°F
说明:
Console.Write() 输出提示信息,不换行Console.ReadLine() 读取用户输入的一行文本double.Parse() 将字符串转换为浮点数9.0 / 5.0 使用浮点数除法,避免整数除法{fahrenheit:F1} 格式化输出,保留1位小数10. 倒计时器练习
编写一个倒计时程序,实现以下功能:
Console.Write() 提示用户输入倒计时的秒数Console.ReadLine() 读取用户输入的字符串int.Parse() 或 Convert.ToInt32() 将字符串转换为整数while 循环,从输入的秒数开始倒数到1Console.WriteLine())Thread.Sleep(1000) 让程序暂停1秒(需要 using System.Threading;)try-catch 处理(可选)|using System; using System.Threading; class CountdownTimer { static void Main() { Console.Write("请输入倒计时秒数: "); string input = Console.ReadLine(); int seconds = int.Parse(input); while (seconds > 0) { Console.WriteLine(seconds); Thread.Sleep(1000); // 暂停1秒 seconds--; } Console.WriteLine("时间到"); } }
|请输入倒计时秒数: 5 5 4 3 2 1 时间到
说明:
while 循环先判断条件,再执行循环体Thread.Sleep(1000) 让程序暂停1000毫秒(1秒)seconds-- 递减操作,每次循环减少1seconds > 0 确保倒数到1后停止11. 打卡提醒练习
编写一个学习打卡程序,实现以下功能:
List<(string topic, int minutes)> 来存储学习记录Console.Write() 和 Console.ReadLine())Console.Write() 和 Console.ReadLine())Add() 方法)OrderByDescending() 方法按分钟数从大到小排序foreach 循环遍历排序后的列表,打印每条记录using System.Linq; 来使用 OrderByDescending() 方法|using System; using System.Collections.Generic; using System.Linq; class StudyTracker { static void Main() { List<(string topic, int minutes)> records = new List<(string, int)>(); while (true) { Console.Write("请输入学习主题: "); string topic = Console.ReadLine(); Console.Write("请输入学习分钟数: "); int minutes = int.Parse(Console.ReadLine()); records.Add((topic, minutes)); Console.Write("是否继续输入?(y/n): "); string choice = Console.ReadLine(); if (choice != "y" && choice != "Y") { break; } } // 按分钟数从大到小排序 var sortedRecords = records.OrderByDescending(r => r.minutes); Console.WriteLine("\n学习记录(按分钟数从大到小):"); foreach (var record in sortedRecords) { Console.WriteLine($"{record.topic}: {record.minutes} 分钟"); } } }
|请输入学习主题: C#基础 请输入学习分钟数: 60 是否继续输入?(y/n): y 请输入学习主题: 算法练习 请输入学习分钟数: 45 是否继续输入?(y/n): y 请输入学习主题: 项目实践 请输入学习分钟数: 90 是否继续输入?(y/n): n 学习记录(按分钟数从大到小): 项目实践: 90 分钟 C#基础: 60 分钟 算法练习: 45 分钟
说明:
List<(string, int)> 使用元组语法,存储主题和分钟数while (true) 创建无限循环,通过 break 退出OrderByDescending() 按指定字段降序排序foreach 遍历排序后的集合record.topic 和 record.minutesusing System.Linq; 来使用 LINQ 方法