PandHedge

C#

2025-09-13
PandHedge
C#

C#

输入与输出

Console.WriteLine() 和Console.Write()

Console.ReadLine() 和Console.Read()

// 最简单的存储方式 储存字符
Console.Write("请输入内容: ");
string userInput = Console.ReadLine();

Console.WriteLine($"您输入的内容是: {userInput}");
Console.WriteLine($"存储长度: {userInput.Length} 字符");
// 存储为整数
Console.Write("请输入年龄: ");
if (int.TryParse(Console.ReadLine(), out int age))
{
    Console.WriteLine($"10年后您将 {age + 10} 岁");
}
else
{
    Console.WriteLine("输入的不是有效数字");
}

// 存储为浮点数
Console.Write("请输入价格: ");
if (decimal.TryParse(Console.ReadLine(), out decimal price))
{
    Console.WriteLine($"含税价格: {price * 1.1m:C}");
}

// 存储为布尔值
Console.Write("是否同意条款 (y/n): ");
bool agreed = Console.ReadLine()?.ToLower() == "y";
Console.WriteLine($"同意状态: {agreed}");

// 存储多个输入到列表
List<string> inputs = new List<string>();

Console.WriteLine("输入多行内容 (空行结束):");
while (true)
{
    string line = Console.ReadLine();
    if (string.IsNullOrWhiteSpace(line)) break;
    
    inputs.Add(line);
}

Console.WriteLine($"\n您输入了 {inputs.Count} 行:");
foreach (var item in inputs)
{
    Console.WriteLine($"- {item}");
}

public class UserProfile
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

// 创建并存储用户档案
UserProfile user = new UserProfile();

Console.Write("姓名: ");
user.Name = Console.ReadLine();

Console.Write("年龄: ");
user.Age = int.Parse(Console.ReadLine()); // 实际应用中应使用 TryParse

Console.Write("邮箱: ");
user.Email = Console.ReadLine();

Console.WriteLine($"\n用户档案创建成功:\n{user.Name}, {user.Age}岁\n邮箱: {user.Email}");


//处理空值
string input = Console.ReadLine() ?? string.Empty;

Console.ReadLine()?.Trim().ToLower()) // 处理空值并标准化输入
    //包含更多清理操作
    var cleanInput = Console.ReadLine()?
    .Trim()                          // 移除前后空格
    .ToLower()                       // 统一小写
    .Replace(" ", "")                // 移除中间空格
    .Replace("\t", "");              // 移除制表符

注释 // /* …..*/

类 方法 参数

数据类型

常量 变量

字符 char ‘b’

字符串 string “Hello”

整数 int 123

浮点 float 123F 123(默认) 123m(十进制) 精度区别

float ~6-9 digits double ~15-17 digits decimal 28-29

布尔 bool true

//变量类型 
            // 一定要死记硬背 各种变量类型的关键字 
            // 一定要记忆 各种不同变量类型 所能存储的范围
            // 一定要记住 各种不同变量类型 所能存储的类型

            //1.有符号的整形变量 是能存储 一定范围 正负数包括0的变量类型
            // sbyte -128~127
            sbyte sb = 1;
            //潜在知识点 通过+来进行拼接打印
            Console.WriteLine("sbyte变量sb中存储的值是:" + sb);
            // int  -21亿~21亿多
            int i = 2;
            // short -32768~32767之间的数
            short s = 3;
            // long -9百万兆~9百万兆之间的数
            long l = 4;

            //2.无符号的整形变量 是能存储 一定范围 0和正数的变量类型
            // byte 0~255
            byte b = 1;
            // uint 0~42亿多的一个范围
            uint ui = 2;
            // ushort 0~65535之间的一个数
            ushort us = 3;
            // ulong 0~18百万兆之间的数
            ulong ul = 4;


            //3.浮点数(小数)
            //float 存储7/8位有效数字 根据编译器不同 有效数字也可能不一样 四舍五入
            //有效数字 是从左到右从非0数开始算有效数字的
            //之所以要在后面加f 是因为c#中 申明的小数 默认是double的类型 加f 是告诉系统 它是float类型
            float f = 1.01234567890f;
            Console.WriteLine(f);
            //double 存储15~17位有效数字 抛弃的数字 会四舍五入
            double d = 0.12345678901234567890123456789;
            Console.WriteLine(d);
            //decimal 存储27~28位的有效数字 不建议使用
            decimal de = 0.123456789012345678901234567890m;
            Console.WriteLine(de);

            //4.特殊类型
            //bool true false 表示真假的数据类型  真假类型
            bool bo = true;
            bool bo2 = false;
            Console.WriteLine(bo + "_" + bo2);

            //char 是用来存储单个字符的变量类型 字符类型
            char c = '唐';
            Console.WriteLine(c);

            //string 是字符串类型 用来存储多个字符的 没有上限
            string str = "的骄傲了肯定就发生123123sdafjkasdkfjaskldjfAKKSAJD";
            Console.WriteLine(str);


            int x = 1000;
            Console.WriteLine(x);
数据类型 大小(字节) 范围/说明
bool 1 truefalse(底层存储通常为 1 字节)
byte 1(2^8) 0 到 255
sbyte 1 -128 到 127
char 2 Unicode 字符(UTF-16 编码),范围 U+0000 到 U+FFFF
short 2 -32,768 到 32,767
ushort 2 0 到 65,535
int 4 -2,147,483,648 到 2,147,483,647
uint 4 0 到 4,294,967,295
float 4 ±1.5e−45 到 ±3.4e38(约 6-9 位有效数字)
long 8 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
ulong 8 0 到 18,446,744,073,709,551,615
double 8 ±5.0e−324 到 ±1.7e308(约 15-17 位有效数字)
decimal 16 ±1.0e−28 到 ±7.9e28(精确十进制,28-29 位有效数字)
IntPtr/UIntPtr 4 或 8 平台相关:32 位系统为 4 字节,64 位系统为 8 字节(用于存储指针或句柄)

###

在 C 中执行基本字符串格式设置

转义

\n \t \

逐字字符串字面量

@

Console.WriteLine(@” c:\source\repos (this is where your code goes)”);

Unicode 转义字符

\u3053

字符串操作

避免中间变量

字符串串联 +

字符串内插 string message = $”{greeting} {firstName}!”;

对 C 中的数字执行基本作

+ - * \ %

+= -= * = ++ –

Console.WriteLine(firstName + “ sold “ + widgetsSold + 7 + “ widgets.”);

编译器将所有内容视为字符串并将其全部连接起来,

应避免在单个代码行中同时执行计算和串联。

递增和递减运算符都有一个有趣的性质——根据它们的位置,它们会在检索其值之前或之后执行操作。 换言之,如果像在 ++value 中一样在值之前使用运算符,则递增会在检索值之前出现。 同样, value++ 将在检索值后递增该值。

强制转换

decimal quotient = (decimal)first / (decimal)second;

.NET 入门

  • 编写用于调用 .NET 类库中的方法的代码。
  • 使用 .NET 类库类的新实例,以调用维护状态的方法。
  • 使用 Visual Studio Code 中的 Intellisense 深入了解方法,包括其重载版本、其返回值类型及其输入参数数据类型。
  • 使用 learn.microsoft.com 来研究方法的作用、其重载版本、其返回值类型、其输入参数和每个参数所代表的内容,等等。

.NET 类库

创建控制台

dotnet new console -o ./CsharpProjects/TestProject

有状态方法与无状态方法

状态”这个词用于描述执行环境在特定时刻的状态。

在执行过程中的任何时候,应用程序的当前状态为存储在内存中的所有值的集合。

某些方法的正常工作不依赖于应用程序的当前状态。 换言之,实现无状态方法是为了在不引用或更改内存中存储的任何值的情况下正常工作。 无状态方法也称为静态方法。例如,Console.WriteLine() 方法不依赖于内存中存储的任何值。 该方法执行其函数并完成工作,而不会以任何方式影响应用程序的状态。

但是,其他方法必须有权访问应用程序的状态,才能正常工作。 换言之,有状态方法的构建方式使得这些方法依赖于先前已执行的代码行存储在内存中的值。 或者,它们通过更新值或将新值存储在内存中来修改应用程序的状态。 它们也称为实例方法。

是,需要调用有状态方法时,必须首先创建类的实例,这样方法才能访问状态。

创建类的实例

类的实例称为 对象

new 运算符执行以下几项重要操作:

  • 首先请求足够大的计算机内存地址,用于存储基于 Random 类的新对象。
  • 创建新的对象,并将其存储在内存地址上。
  • 它返回内存地址,以便它可以保存在 dice 对象中。

方法的返回值和参数

返回值

一些方法旨在完成其功能并“安静地”结束。 换言之,它们在完成时不返回值。 这些方法称为 void 方法。

其他方法旨在完成后返回值。 返回值通常是某个操作的结果。 返回值是方法与调用方法的代码进行通信的主要方式。

调用语句中的方法参数和自变量

调用某个方法时,可以传入该方法将用于完成其任务的值。 这些值称为参数。

方法使用“方法签名”来定义该方法将接受的参数数量以及每个参数的数据类型。

“重载方法”是通过多个方法签名定义的。 重载方法提供不同的方式调用方法,或提供不同类型的数据。fg

使用 C# 中的“if”、“else”和“else if”语句向代码添加决策逻辑

  或 && 与

代码块

{ } 代码块是包含一行或多行代码的集合

折叠代码
#region 介绍

#endregion

将中间代码包裹起来,只有编辑时有用

使用 if、else if 和 else 创建嵌套决策逻辑

使用 C# 中的数组和 foreach 语句来存储和循环访问数据序列

在本模块中,你将:

  • 创建并初始化新数组。
  • 在数组中设置和获取值。
  • 使用 foreach 语句循环访问数组的每个元素。

什么是数组

数组是可通过单个变量名称进行访问的单个数据元素的集合。 使用从零开始的数字索引访问数组中的每个元素。 数组允许你以单个变量名称创建用途或特征相同的数据值的集合,以便于处理。

数组是一种特殊类型的变量,可以保存同一数据类型的多个值。 因为必须同时指定数组的数据类型和大小,所以数组的声明语法略有不同。

声明新数组

string[] fraudulentOrderIDs = new string[3];

string[] fraudulentOrderIDs = [ “A123”, “B456”, “C789” ];

使用数组的 Length 属性

Length 属性(整数)返回 fraudulentOrderIDs 数组中的元素数目

使用 foreach 遍历数组

foreach 语句以递增索引顺序(从索引 0 开始并以索引 Length - 1 结束)处理数组元素。 它使用临时变量来保存与当前迭代关联的数组元素的值。 每次迭代都将运行位于 foreach 声明下方的代码块。

使用 C# 创建具有约定、空格和注释的易读代码

变量名称规则

  • 变量名称可以包含字母数字字符和下划线 (_) 字符。 不允许使用英镑 #、短划线 -和美元符号 $ 等特殊字符。
  • 变量名称必须以字母或下划线开头,而不是数字。 使用下划线字符启动变量名称通常是为专用实例字段保留的。 可以在模块摘要中找到指向进一步阅读的链接。
  • 变量名称不得为 C# 关键字。 例如,不允许这些变量名称声明: float float;string string;
  • 变量名称区分大小写,这意味着 string MyValue;string myValue; 两个不同的变量。

变量名称约定

  • 变量名称应使用驼峰式大小写形式,这是一种书写样式,即第一个单词的首字母采用小写形式,后续每个单词的首字母则采用大写形式。 例如: string thisIsCamelCase;
  • 变量名称在应用程序中应具有描述性且有意义。 应为变量选择一个名称,该名称表示它将保留的数据类型(而不是数据类型)。 例如: bool orderComplete;,NOT bool isComplete;
  • 变量名称应是组合在一起的一个或多个完整单词。 请勿使用缩写形式,因为变量名称可能对正在阅读代码的其他人而言不够清楚。 例如: decimal orderAmount;,NOT decimal odrAmt;
  • 变量名称不应包含变量的数据类型。 你可能会看到一些建议使用类似string strMyValue;的样式。 几年前,这是一种流行的风格。 但是,大多数开发人员不再遵循此建议,并且有充分的理由不要使用它。

注释

本练习的要点包括:

  • 使用代码注释为自己添加有意义的备注,注明代码可解决的问题。
  • 请勿使用解释 C# 或 .NET 类库如何工作的代码注释。
  • 暂时尝试替代解决方法时,请使用代码注释,直至你已准备提交新的代码解决方案,此时可以删除旧代码。
  • 不要完全相信注释。 在进行许多更改和更新之后,它们可能不会反映代码的当前状态。

计算布尔表达式以在 C# 中做出决策

== 相等 != 不等 ! 逻辑非

ToUpper() 全部大写 ToLower() 全部小写

Trim() 删除前置或尾随空格

什么是条件运算符?

? <if condition is true, return this value> : <if condition is false, return this value> 条件运算符 计算布尔表达式,并根据布尔表达式的计算结果是 true 还是 false,返回两个结果之一`?:`。 条件运算符通常称为三元条件运算符。 #### 在 C# 中使用代码块控制变量范围和逻辑 #### 代码块和变量范围 变量范围是指变量对应用程序中其他代码的可见性。 局部变量只能在定义它的代码块内进行访问。 如果尝试在代码块外部访问变量,则将出现编译器错误。 - 在代码块内声明变量时,其可见性对该代码块为本地,并且不能在代码块之外访问该变量。 - 要确保变量在代码块内外都可见,必须在代码块前面(代码块外部和上方)声明该变量。 - 确保在代码尝试访问变量之前初始化变量(对于所有可能的代码执行路径)。 - ## 使用 C# 中的 switch-case 结构将代码流分支 ```c# switch (fruit) { case "apple": Console.WriteLine($"App will display information for apple."); break; default: title = "Associate"; break; } ``` - `switch`如果有一个值具有多个可能的匹配项,则使用该语句,每个匹配项都需要代码逻辑中的分支。 - 可以使用通过 `case` 关键字定义的一个或多个标签来匹配单个包含代码逻辑的开关部分。 - 使用可选的 `default` 关键字创建一个标签和一个 switch 语句,当没有其他 case 标签匹配时,此语句将被使用。 ## 使用 C# 中的 for 语句循环访问代码块 该 `for` 语句遍历代码块特定次数。 此级别的控制使 `for` 语句在其他迭代语句中是唯一的。 该 `foreach` 语句对数组或集合等数据序列中的每个项循环访问一次代码块。 语句 `while` 循环访问代码块,直到满足条件。 ```c# for (int i = 0; i < 10; i++) { Console.WriteLine(i); } ``` ```c# string[] names = { "Alex", "Eddie", "David", "Michael" }; for (int i = names.Length - 1; i >= 0; i--) { Console.WriteLine(names[i]); } ``` #### foreach 语句的限制 您无法重新赋值`name`,因为它是`foreach`迭代的内部实现的一部分。 ## 使用 C# 中的 do-while 和 while 语句在代码中添加循环逻辑 使用 `do-while` 和 `while` 语句可以通过循环访问代码块来控制代码执行流,直到满足某个条件为止。 在使用 `foreach` 语句时,我们按顺序对每个项循环访问一次,例如数组。 使用 `for` 语句可以迭代预定次数并控制迭代过程。 使用 `do-while` 和 `while` 语句可以循环访问代码块,直到代码块内的逻辑可以影响停止循环访问的时间。 假设你要接受和处理用户的输入。 你想要继续接受和处理输入,直到用户按 `q` 键“退出”。 你可以使用 `do-while` 和 `while` 语句循环访问逻辑,以接受和处理用户输入,直到用户准备停止。 ```c# do { // This code executes at least one time } while (false) { }; ``` 执行流在大括号内开始。 代码至少执行一次,然后计算 `while` 关键字旁边的布尔表达式。 如果布尔表达式返回 `true`,则再次执行代码块。 通过将布尔表达式硬编码为 `true`,我们创建了一个无限循环,该循环将永不结束,至少在当前编写后不会。 我们需要一种方法来跳出代码块内部的循环。 我们稍后会讨论 `do-while` 的退出条件。 #### 使用 continue 语句直接跳到布尔表达式 #### 区分 do 和 while 迭代语句 如前所述,C# 支持四种类型的迭代语句:`for`、`foreach`、`do-while` 和 `while`。 Microsoft 的语言参考文档介绍了这些语句,如下所示: - `for` 语句:在指定的布尔表达式 ('condition') 计算结果为 true 时执行其主体。 - `foreach` 语句:枚举集合元素并对集合中的每个元素执行其主体。 - `do-while` 语句:有条件地执行其主体一次或多次。 - `while` 语句:有条件地执行其主体零次或多次。 #### 方法 `int.TryParse()` 方法 `int.TryParse()` 可用于将字符串值转换为整数。 该方法使用两个参数,一个是将计算的字符串,另一个则是将会赋值的整数变量的名称。 此方法将返回布尔值。 validNumber = int.TryParse(readResult, out numericValue); 如果分配给 `readResult` 的字符串值表示有效的整数,则该值将赋给名为 `numericValue` 的整数变量,并将 `true` 分配给名为 `validNumber` 的布尔变量。 如果分配给 `readResult` 的值不表示有效的整数,则 `validNumber` 将被赋给 `false` 的值。 例如,如果 `readResult` 等于“7”,则将值 `7` 赋给 `numericValue`。 ## C#中的值与引用 简单的值类型:char bool int decimal .... 引用类型:数组\类\字符串... 区别:值类型储存值的数额较小,储存在堆栈当中 引用类型储存的值较大,在 堆 中 存储 选取适当的数据类型:边界值 优化性能 库函数的输入输出要求 与其他系统的影响 ## 在 C# 中使用强制转换和转换技术转换数据类型 对数据类型\对变量\对Convert 扩大转换\ 收缩转换(强制转换)\Convert 强制转换:截断 Convert:四除五进 (int)value int.Tostring() :int - string int.Parse():string - int Convert.ToInt32() #### TryParse() 方法 ``` C# string name = "Bob"; Console.WriteLine(int.Parse(name)); ``` TryParse() 方法可同时执行多项操作: - 它会尝试将字符串分析成给定的数字数据类型。 - 如果成功,它会将转换后的值存储在 out 参数中,如以下部分所述。 - 它将返回 `bool`,指示操作是成功还是失败。 #### 数据转换的三种方式 ```c# int value1 = 11; decimal value2 = 6.2m; float value3 = 4.3f; // The Convert class is best for converting the fractional decimal numbers into whole integer numbers // Convert.ToInt32() rounds up the way you would expect. int result1 = Convert.ToInt32(value1 / value2); Console.WriteLine($"Divide value1 by value2, display the result as an int: {result1}"); decimal result2 = value2 / (decimal)value3; Console.WriteLine($"Divide value2 by value3, display the result as a decimal: {result2}"); float result3 = value3 / value1; Console.WriteLine($"Divide value3 by value1, display the result as a float: {result3}"); ``` ## 使用 C #中的帮助程序方法对数组执行作 ---- ----- ## 规划宠物动物园访问 ----- ---- ## 挑战项目 - 创建迷你游戏 ```目标 在本模块中,你将开发小型游戏应用程序的以下功能: 用于确定玩家是否消耗了食物的功能 根据消耗的食物更新玩家状态的功能 一项功能,根据消耗的食物暂停运动速度 在新位置重新生成食物的功能 按下不受支持的字符时终止游戏的选项 在调整终端窗口大小时终止游戏的功能 ``` #### 临时目标 ``` 调整大小时终止 此功能必须实现以下目标: 在允许游戏继续之前,确定是否调整了终端的大小 如果终端的大小已调整,清除控制台并结束游戏 在结束程序之前显示以下消息:Console was resized. Program exiting. 添加可选的终止 修改现有的 Move 方法以支持可选参数 如果启用,可选参数应检测非方向键输入 如果检测到非方向输入,允许游戏终止 ``` ------- ----- ## 查看代码调试和异常处理的原则 ## 学习目标 在此模块中,你将: - 查看软件测试、调试和异常处理的作用。 - 检查代码调试过程以及代码调试器工具提供的优势。 - 检查什么是异常,以及用于在代码中管理异常的选项。 ### 软件测试和开发人员职责 软件开发过程可能涉及大量测试。 事实上,软件测试有自己的专业学科,软件测试人员在开发大型应用程序方面发挥了重要作用。 甚至还有一些基于测试的软件开发过程的方法,例如测试驱动开发。 软件测试类别可以按测试 *类型* 、测试 *方法* 或两者的组合进行组织。 对测试类型进行分类的一种方法是将测试拆分为 *功能* 测试和非 *功能* 测试。 每个函数类别和非功能类别都包含测试的子类别。 例如,功能和非功能测试可以分为以下子类别: - 功能测试 - 单元测试 - 集成测试 - 系统测试 - 验收测试 - 非功能测试 - 安全测试 - 性能测试 - 可用性测试 - 兼容性测试 尽管大多数开发人员可能不认为自己是测试人员,但在开发人员将工作移交之前,预期会进行某种级别的测试。 开发人员被分配到测试过程中的正式角色时,通常是在单元测试级别。 ### 代码调试和开发人员职责 代码调试是开发人员用来隔离问题并识别一种或多种解决问题的方法的过程。 此问题可能与代码逻辑或异常相关。 不管怎样,当代码无法按所需方式工作时,你都需要调试它。 一般来说,术语“调试”是为不容易隔离的运行时问题保留的。 因此,修复语法问题(如代码语句末尾缺少的“;”)通常不被视为调试。 ### 检查代码调试器调试代码的方法 #### 为什么使用调试器 如果不通过调试器运行代码,这意味着你可能猜测应用程序在运行时会发生什么。 使用调试器的主要好处是可以监视程序运行。 可以一次跟踪一个程序代码行的执行。 此方法可最大程度地减少猜错的可能性。 每个调试器都有其自己的一组功能。 几乎所有调试器所具有的两个最重要的功能是: - 控制程序执行。 你可以暂停程序并逐步运行它,以便查看执行了哪些代码及其对程序状态的影响。 - 观察程序的状态。 例如,你可以在代码执行期间随时查看变量的值和函数参数。 ### 检查异常以及异常的使用方式 #### 什么是异常? *在 C# 中,程序中的运行时错误通过使用一种称为“异常”的机制在程序中传播。 异常由遇到错误的代码引发,由能够更正错误的代码捕捉。 异常可由 .NET 运行时或由程序中的代码引发。 异常由从 Exception 派生的类表示。 每个类标识异常的类型,并包含详细描述异常的属性。* #### “引发”和“捕获”异常意味着什么? 术语“引发”和“捕获”可以通过评估异常的定义来解释。 定义的第二句话显示“异常由遇到错误的代码引发,并由可更正错误的代码捕获”。 该句子的第一部分表示,当代码中发生错误时,.NET 运行时会创建异常。 句子的第二部分表示,可以编写代码来捕获引发的异常。 此外,捕获异常的代码可用于完成纠正措施,希望能够缓解引起错误的代码所导致的情况。 换句话说,你可以编写代码,以便在发生错误时保护应用程序。 在评估定义的第二个句子后,你将了解到以下内容: - 当代码生成错误时,会在运行时创建异常。 - 可以将异常视为具有一些额外功能的变量。 - 可以编写访问异常并采取纠正措施的代码。 定义的剩余部分告知如果 .NET 运行时检测到错误,则会生成异常。 生成的异常包含有关发生的错误的信息。 代码可以使用异常中存储的信息捕获异常并更正问题。 ## 实现适用于 C 的 Visual Studio Code 调试工具# “ **运行** ”菜单提供分为六个部分的菜单选项。 1. 启动和停止应用程序。 此菜单部分包括用于在附加和未附加调试器情况下启动和停止代码执行的选项。 2. 启动配置。 菜单的此部分提供检查或创建启动配置的访问权限。 3. 运行时控制 菜单的此部分使开发人员能够控制他们想要通过代码前进的方式。 在调试会话期间,当执行暂停时,将启用控件。 4. 设置断点。 菜单的此部分使开发人员能够在代码行上设置断点。 在调试会话中,代码会在断点处暂停执行。 5. 管理断点。 菜单的此部分使开发人员能够批量管理断点,而不是单独管理断点。 6. 安装调试器。 菜单的这一部分将打开针对代码调试器筛选的 Visual Studio Code“扩展”视图。 ### 调试器和应用程序交互 代码调试器可用于暂停和恢复代码执行、检查变量状态,甚至可以在运行时更改分配给变量的值。调试器有权访问应用程序的运行时环境和可执行代码。 适用于 C# 的 Visual Studio Code 调试器使用 .NET Core 运行时启动应用程序并与之交互。 启动调试器时,它会创建运行时的新实例,并在该实例中运行应用程序。 运行时包括应用程序编程接口(API),调试器使用该接口附加到正在运行的进程(应用程序)。 应用程序运行并附加调试器后,调试器将使用 .NET Core 运行时的调试 API 和标准调试协议与正在运行的进程通信。 调试器可以通过设置断点、单步执行代码和检查变量来与进程(在 .NET 运行时实例中运行的应用程序)进行交互。 Visual Studio Code 的调试器接口使你能够导航源代码、查看调用堆栈和计算表达式。 指定调试会话的最常见方法是 launch.json 文件中的启动配置。 此方法是调试器工具启用的默认选项。 例如,如果创建 C# 控制台应用程序并从“运行”菜单中选择“开始调试”,调试器将使用此方法启动、附加到应用程序,然后与应用程序交互。 --------------- Visual Studio Code 使用启动配置文件来指定在调试环境中运行的应用程序。 sln文件是 Visual Studio 用来管理项目的解决方案文件,通常在 Visual Studio Code 中创建新项目时自动创建。 调试器使用.sln文件来标识应在调试环境中运行的项目。 ----------- 断点 条件断点 命中次数断点 日记点 ## 检查启动配置文件 launch.json 文件包括在 configurations 列表中的一个或多个启动配置。 启动配置使用属性来支持不同的调试方案。 对于每个启动配置,以下属性是必需的: name:分配给启动配置的用户友好名称。 type:用于启动配置的调试器的类型。 request:启动配置的请求类型。 ### 名称 该 `name` 属性指定启动配置的显示名称。 分配给 `name` 的值将显示在“启动配置”下拉列表中(在“运行和调试”视图顶部的控制面板上)。 ### 类型 该 `type` 属性指定要用于启动配置的调试器的类型。 一个值 `codeclr` ,指定 .NET 5+ 和 .NET Core 应用程序的调试器类型(包括 C# 应用程序)。 ### 请求 该 `request` 属性指定启动配置的请求类型。 目前,支持的值为 `launch` 和 `attach`。 ### PreLaunchTask 该 `preLaunchTask` 属性指定要在调试程序之前运行的任务。 任务本身可以在 tasks.json 文件中找到,该文件 `.vscode` 与 launch.json 文件位于文件夹中。 指定`build`的预启动任务会在启动应用程序前运行`dotnet build`命令。 ### 程序/项目 该 `program` 属性设置为要启动的应用程序 dll 或 .NET Core 主机可执行文件的路径。 此属性通常采用以下形式: `${workspaceFolder}/bin/Debug//`。 地点: - `` 是用于生成调试项目的框架。 此值通常在项目文件中找到为“TargetFramework”属性。 - `` 是调试项目的生成输出 dll 的名称。 此属性通常与项目文件名相同,但扩展名为“.dll”。 例如:`${workspaceFolder}/bin/Debug/net7.0/Debug101.dll` ### cwd 该 `cwd` 属性指定目标进程的工作目录。 ### 参数 该 `args` 属性指定在启动时传递给程序的参数。 默认情况下没有参数。 ### 控制台 该 `console` 属性指定启动应用程序时使用的控制台类型。 选项为 `internalConsole`、`integratedTerminal` 和 `externalTerminal`。 默认设置为 `internalConsole`。 控制台类型定义为: - 该 `internalConsole` 设置对应于 Visual Studio Code 编辑器下方“面板”区域中的“调试控制台”面板。 - 该 `integratedTerminal` 设置对应于 Visual Studio Code 编辑器下方“面板”区域中的“输出”面板。 - 该 `externalTerminal` 设置对应于外部终端窗口。 Windows 附带的命令提示符应用程序是终端窗口的一个示例。 重要 “调试控制台”面板不支持控制台输入。 例如,如果应用程序包含 Console.ReadLine() 语句,则不能使用 DEBUG 控制台。 在编写读取用户输入的 C# 控制台应用程序时,必须将 console 设置为 integratedTerminal 或 externalTerminal。 写入控制台但未从控制台读取输入的控制台应用程序可以使用这三 console 个设置中的任何一个。 在入口处停止 如果需要在目标的入口点停止,可以选择将 stopAtEntry 设置为 true。 ### 更新启动配置以适应多个应用程 假设你正在处理包含多个控制台应用程序的编码项目。 根项目文件夹 **SpecialProjects** 是在处理代码时在 Visual Studio Code 中打开的工作区文件夹。 你有两个要开发的应用程序,Project123 和 Project456。 使用“运行和调试”视图调试应用程序。 你想要从用户界面中选择要调试的应用程序。 还需要在将调试器附加到应用程序之前编译任何已保存的代码更新。 请注意, `.vscode` 包含 launch.json 和 tasks.json 文件的文件夹与工作区文件夹 **(SpecialProjects**)而不是单个项目文件夹相关联。 请注意, **名称**、 **preLaunchTask** 和 **程序** 字段都配置为特定应用程序。 **名称**属性指定在 RUN AND DEBUG 视图用户界面中显示的可选启动选项,**程序**属性指定应用程序的路径。 **preLaunchTask** 属性用于指定启动调试器之前执行的任务的名称。 **tasks.json** 文件包含命名任务以及完成任务所需的信息。 # 检查异常和异常处理过程 需要异常处理的常见方案 有几个编程方案需要异常处理。 其中许多方案涉及某种形式的数据收集。 尽管某些方案涉及此训练范围之外的编码技术,但它们仍值得注意。 需要异常处理的常见方案包括: 用户输入:代码处理用户输入时可能发生异常。 例如,当输入值的格式不正确或范围不足时,会发生异常。 数据处理和计算:当代码执行数据计算或转换时,可能会发生异常。 例如,当代码尝试除以零、强制转换为不受支持的类型或赋值超过范围时,会发生异常。 文件输入/输出作:当代码从文件中读取或写入文件时,可能会发生异常。 例如,当文件不存在、程序无权访问该文件或文件被另一个进程使用时,会发生异常。 数据库操作:当代码与数据库交互时,可能会发生异常。 例如,当数据库连接丢失、SQL 语句中发生语法错误或发生约束冲突时,会发生异常。 网络通信:当代码通过网络通信时,可能会发生异常。 例如,当网络连接丢失、超时或远程服务器返回错误时,会发生异常。 其他外部资源:当代码与其他外部资源通信时,可能会发生异常。 由于各种原因,Web 服务、REST API 或第三方库可能会引发异常。 例如,由于网络连接问题、格式不正确的数据等,会出现异常。 #### 异常处理关键字、代码块和模式 C# 中的异常处理是通过使用 `try`、`catch` 和 `finally` 关键字实现的。 其中每个关键字都有一个关联的代码块,可用于满足异常处理方法中的特定目标。 `try` 代码块包含可能导致异常的受保护的代码。 如果块中的 `try` 代码导致异常,则异常由相应的 `catch` 块处理。 `catch` 代码块包含捕获异常时执行的代码。 该 `catch` 块可以处理异常、记录异常或忽略异常。 `catch`可以将块配置为在发生任何异常类型时执行,或仅在发生特定类型的异常时执行。 `finally` 代码块包含无论是否发生异常都要执行的代码。 `finally` 块通常用于清理在 `try` 块中分配的任何资源。 例如,确保变量具有分配给变量的正确值或必需值。 C# 应用程序中的异常处理通常使用以下一个或多个模式实现: - `try-catch` 模式包含一个 `try` 块,后跟一个或多个 `catch` 子句。 每个 `catch` 块用于指定不同异常的处理程序。 - 该 `try-finally` 模式由一个 `try` 块组成,然后紧随一个 `finally` 块。 通常,当控制离开 `try` 语句时,`finally` 块的语句将运行。 - 该 `try-catch-finally` 模式实现所有三种类型的异常处理块。 模式中 `try-catch-finally` 的常见场景是:在 `try` 块中获取和使用资源,在 `catch` 块中处理异常情况,并在 `finally` 块中释放或管理资源。 ``` try { // try code block - code that may generate an exception } catch { // catch code block - code to handle an exception } finally { // finally code block - code to clean up resources } ``` 备注 C# 语言还允许代码使用 `throw` 关键字生成异常对象。 包括使用 `throw` 关键字生成异常的异常处理方案在 Microsoft Learn 上的单独模块中介绍。 #### 如何在代码中表示异常? 异常在代码中表示为对象,这意味着它们是类的实例。 .NET 类库提供在代码中访问的异常类,就像其他 .NET 类一样。 用作代码中对象的 .NET 类的另一个示例是 `Random` 类(用于创建随机数)。 更确切地说,异常情况是由所有最终派生自 `System.Exception` 的类所表示的类型。 派生自 `Exception` 的异常类包括标识异常类型的信息,并包含提供有关异常的详细信息的属性。 本模块后面的部分将对 `Exception` 类进行更详细的探讨。 类的运行时实例通常称为对象,因此异常通常称为异常对象。 ## 异常处理过程 当发生异常时,.NET 运行时会查找最近的能够处理该异常的catch子句。 该过程从导致引发异常的方法开始。 首先,检查该方法,以查看导致异常的代码是否在代码块内 try 。 如果代码位于 try 代码块内部,则按顺序考虑与 try 语句关联的 catch 子句。 若catch子句无法处理异常,则会搜寻调用当前方法的方法。 通过检查此方法来确定对第一个方法的调用是否在try代码块内。 如果调用位于 try 代码块内,则会考虑关联的 catch 子句。 此搜索过程将继续执行,直到找到一个可以处理当前异常的 catch 子句。 找到可以处理异常的 catch 子句后,运行时会准备将控制权转移到 catch 块的第一个语句。 但是,在开始执行 catch 块之前,运行时将执行与搜索期间找到的 try 语句关联的任何 finally 块。 如果找到多个 finally 块,则按顺序执行它们,从最接近导致引发异常的代码开始。 catch如果未找到任何子句来处理异常,运行时将终止应用程序并向用户显示错误消息。 ## 异常处理和调用堆栈 ## 编译器生成的异常 当基本操作失败时,.NET 运行时会引发异常。 下面是运行时异常及其错误条件的简短列表: - `ArrayTypeMismatchException`:当数组无法存储给定元素时引发,因为该元素的实际类型与数组的实际类型不兼容。 - `DivideByZeroException`:尝试将整数值除以零时引发。 - `FormatException`:参数格式无效时引发。 - `IndexOutOfRangeException`:在索引小于零或超出数组边界的情况下,尝试为数组编制索引时引发。 - `InvalidCastException`:当从基类型到接口或派生类型的显式转换在运行时失败时引发。 - `NullReferenceException`:尝试引用值为 null 的对象时引发。 - `OverflowException`:当被选中上下文中的算术运算溢出时引发。 请注意,由于异常是在 `Process1` 内部被捕获的,因此顶级语句中的 `catch` 代码块不会被执行。 当捕获特定异常类型时,捕获调用堆栈中不同级别的异常所带来的好处变得更加明显。 你将在下一个单元中检查异常类型。 ## 检查异常属性 `System.Exception` 是所有派生异常类型继承自的基类。 每个异常类型都通过特定的类层次结构从基类继承。 例如,类 `InvalidCastException` 层次结构如下所示: ``` Object Exception SystemException InvalidCastException ``` 下面是类的属性 `Exception` : - **数据**:该 `Data` 属性在键值对中保存任意数据。 - **HelpLink**:`HelpLink` 属性可用于保存包含 URL(或 URN)的帮助文件,提供有关异常原因的详细信息。 - **HResult**:该 `HResult` 属性可用于访问分配给特定异常的编码数值。 - **InnerException**:属性 `InnerException` 可用于在异常处理期间创建和保留一系列异常。 - **消息**:该 `Message` 属性提供有关异常原因的详细信息。 - **源**:该 `Source` 属性可用于访问应用程序的名称或导致错误的对象。 - **StackTrace**:该 `StackTrace` 属性包含可用于确定发生错误的堆栈跟踪。 - **TargetSite**:该 `TargetSite` 属性可用于获取引发当前异常的方法。 `catch`尽管该子句可以在不使用参数的情况下使用,但不建议使用此方法。 如果未指定参数,将捕获所有异常类型,并且无法在它们之间进行区分。 一般情况下,只应捕获代码知道如何从中恢复的异常。 因此,子 `catch` 句应指定派生自 `System.Exception`的对象参数。 异常类型应尽可能具体。 这有助于避免捕获异常处理程序无法解析的异常。 你将在本练习的后半部分更新代码以捕获特定异常类型。 ## 在 C# 控制台应用程序中创建和引发异常 下面是在创建异常时可以使用的一些常见异常类型: 下面是在创建异常时可以使用的一些常见异常类型: - `ArgumentException` 或 `ArgumentNullException`:当使用无效参数值或 null 引用调用方法或构造函数时,请使用这些异常类型。 - `InvalidOperationException`:当方法的作条件不支持成功完成特定方法调用时,请使用此异常类型。 - `NotSupportedException`:在操作或功能不受支持时使用此异常类型。 - `IOException`:当输入/输出作失败时,请使用此异常类型。 - `FormatException`:当字符串或数据的格式不正确时,请使用此异常类型。 ``` ArgumentException invalidArgumentException = new ArgumentException(); ``` ## 配置和引发自定义异常 引发异常对象的过程涉及创建异常派生类的实例,可以选择配置异常的属性,然后使用关键字引发对象 `throw` 。 在引发异常之前,使用上下文信息自定义异常通常很有帮助。 可以通过配置异常对象的属性来提供应用程序特定信息。 例如,以下代码创建一个使用自定义`Message`属性命名`invalidArgumentException`的异常对象,然后引发异常: ``` ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range."); throw invalidArgumentException; ``` 备注 异常的 `Message` 属性为只读。 因此,在实例化对象时必须设置自定义 `Message` 属性。 ## 何时引发异常 每当方法无法完成预期目的时,方法都应引发异常。 引发的异常应基于符合错误条件的最具体的可用异常。 ## 重新引发异常 除了可以引发新异常之外,`throw` 还可以在`catch`代码块内部重新引发异常。 在这种情况下,`throw` 不接受异常操作数。 ## 引发异常时应避免的情况 以下列表标识了在抛出异常时应避免的做法: - 不要使用异常来更改程序流作为普通执行的一部分。 使用异常来报告和处理错误状况。 - 不应将异常作为返回值或参数返回,而应该引发异常。 - 不要从自己的源代码中故意抛出`System.Exception`、`System.SystemException`、`System.NullReferenceException`或`System.IndexOutOfRangeException`。 - 不要创建可以在调试模式下引发但不能在发布模式下引发的异常。 若要在开发阶段识别运行时错误,请改用 `Debug.Assert` 。 备注 此方法 `Debug.Assert` 是用于在开发过程中捕获逻辑错误的工具。 默认情况下,该方法 `Debug.Assert` 仅适用于调试生成。 可以在 `Debug.Assert` 调试会话中使用来检查不应发生的条件。 该方法采用两个参数:要检查的布尔条件,以及一条可选的字符串消息,用于显示条件是否为 `false`。 `Debug.Assert` 不应被用来代替抛出异常,抛出异常是在正常执行代码期间处理异常情况的一种方法。 应使用 `Debug.Assert` 捕获不应发生的错误,并使用异常来处理在程序正常执行期间可能发生的错误。 ---- 其他知识 - `async/await`,整个过程不会阻塞 UI 线程,

上一篇 Bash命令

下一篇 Docker基础

Comments

Content