练习 - 探索数据类型强制转换和转换
- 30 分钟
有多种方法可以执行数据类型转换。 选择的方法取决于你对两个重要问题的回答:
- 对于不同的值,尝试更改值的数据类型是否可能在运行时引发异常?
- 对于不同的值,尝试更改值的数据类型是否可能导致信息丢失?
在此练习中,你将研究这些问题、它们的答案的含义,以及需要更改数据类型时应使用哪种方法。
准备编码环境
本模块包括指导你完成生成和运行演示代码的过程的动手活动。 建议使用 Visual Studio Code 作为开发环境来完成这些活动。 使用 Visual Studio Code 来完成这些活动可帮助你在由全球专业人员使用的开发人员环境中更舒适地编写和运行代码。
注意
如果已完成此 C# 系列中的其他 Microsoft Learn 模块,则可能已经为代码示例创建了一个项目文件夹。 如果是这种情况,可以跳过步骤的以下部分,并删除用于上一练习的 Program.cs 文件中的代码。
注意
本练习中的代码示例是根据 en-US 区域性设置而设计的,使用句点 (.) 作为小数点分隔符。 在使用其他小数分隔符(如逗号 ,)的区域性设置下生成和运行代码,可能会出现意外的结果或错误。 若要解决此问题,请将代码示例中的句点小数点分隔符替换为你的本地小数点分隔符(例如 ,)。
或者,若要运行使用“en-US”区域性设置的程序,请将以下代码添加到程序顶部:using System.Globalization;,并在任何其他 using 语句后添加 CultureInfo.CurrentCulture = new CultureInfo("en-US");。
打开 Visual Studio Code。
可以使用 Windows“开始”菜单(或其他 OS 的等效资源)打开 Visual Studio Code。
在 Visual Studio Code 的“文件”菜单上,选择“打开文件夹”。
在“打开文件夹”对话框中,导航到 Windows 桌面文件夹。
如果使用了不同的文件夹位置来保存代码项目,则可以改用该文件夹位置。 对于此培训,重要的是要有一个易于定位和记忆的位置。
在“打开文件夹”对话框中,选择“选择文件夹”。
如果看到一个询问你是否信任作者的安全对话框,请选择“是”。
在 Visual Studio Code 的“终端”菜单上,选择“新终端”。
请注意,“终端”面板中的命令提示符显示当前文件夹的文件夹路径。 例如:
C:\Users\someuser\Desktop>在终端命令提示符处,若要在指定文件夹中新建控制台应用程序,请键入
dotnet new console -o ./CsharpProjects/TestProject,然后按 Enter。此 .NET CLI 命令使用 .NET 程序模板在指定文件夹位置创建新的 C# 控制台应用程序项目。 该命令会为你创建 CsharpProjects 和 TestProject 文件夹,并使用 TestProject 作为
.csproj文件的名称。在“资源管理器”面板中,展开“CsharpProjects”文件夹。
你应会看到 TestProject 文件夹和两个文件,一个名为 Program.cs 的 C# 程序文件和一个名为 TestProject.csproj 的 C# 项目文件。
在“资源管理器”面板中,要在“编辑器”面板中查看代码文件,请选择“Program.cs”。
删除现有代码行。
在本模块中,请使用此 C# 控制台项目来创建、生成和运行代码示例。
关闭“终端”面板。
问题:尝试更改值的数据类型是否可能在运行时引发异常?
C# 编译器会尝试适应你的代码,但不会编译可能导致异常的操作。 了解 C# 编译器的主要问题后,你将更容易理解它以特定方式工作的原因。
编写尝试添加 int 和 string 的代码,并将结果保存在 int 中
确保已打开 Visual Studio Code,并且“编辑器”面板中显示了 Program.cs。
注意
Program.cs 应为空。 如果不是,请选择并删除所有代码行。
在 Visual Studio Code 编辑器中键入以下代码:
int first = 2; string second = "4"; int result = first + second; Console.WriteLine(result);在此,你尝试添加值
2和4。 值4的类型为string。 这是否有效?在 Visual Studio Code 的“文件”菜单上,选择“保存”。
在生成或运行代码之前,必须保存 Program.cs 文件。
在“资源管理器”面板中,若要在 TestProject 文件夹位置打开终端,请右键单击“TestProject”,然后选择“在集成终端中打开”。
终端面板应打开,并应包含一个命令提示符,显示终端已打开并转到 TestProject 文件夹位置。
在终端命令提示符处,若要运行代码,请键入
dotnet run,然后按 Enter。你应会看到以下类似输出
C:\Users\someuser\Desktop\csharpprojects\TestProject\Program.cs(3,14): error CS0029: Cannot implicitly convert type 'string' to 'int'注意
如果看到一条消息,指出“找不到要运行的项目”,请确保终端命令提示符显示预期的 TestProject 文件夹位置。 例如:
C:\Users\someuser\Desktop\csharpprojects\TestProject>请花一点时间考虑编译器无法运行第一个代码示例的原因。
错误消息的重要部分
(3,14): error CS0029: Cannot implicitly convert type 'string' to 'int'指示问题与使用string数据类型有关。但为什么 C# 编译器无法处理该错误? 毕竟,你可以执行反向操作,将数字连接到
string,并将其保存在字符串变量中。 此处,你可以将result变量的数据类型从int更改为string。在 Visual Studio Code 编辑器中更新代码,如下所示:
int first = 2; string second = "4"; string result = first + second; Console.WriteLine(result);保存代码文件,然后使用 Visual Studio Code 运行代码。
应该会看到以下输出:
24输出在数学上不正确,但通过将值组合为字符“2”和“4”来完成。
再次查看第一个代码示例,其中
result变量的类型为int。 包含错误消息的代码。int first = 2; string second = "4"; int result = first + second; Console.WriteLine(result);为什么 C# 编译器不明白你要将包含
4的变量second视为数字,而不是string?
编译器进行安全转换
C# 编译器会在操作过程中发现潜在问题。 变量 second 的类型为 string,因此可能会将其设置为不同的值,如 "hello"。 如果 C# 编译器尝试将 "hello" 转换为数字,则会在运行时引发异常。 为避免这种情况,C# 编译器不会隐式执行从 string 到 int 的转换。
从 C# 编译器的角度来看,更安全的操作是将 int 转换为 string 并改为执行串联。
如果你的目的是使用字符串执行加法,C# 编译器会要求你对数据转换过程进行更显式的控制。 换言之,它会强制要求你更多地参与其中,这样你就可以采取适当的预防措施来处理转换可能引发异常的可能性。
如果需要将值从原始数据类型更改为新的数据类型,并且更改可能在运行时引发异常,则必须执行数据转换。
若要执行数据转换,你可以使用下列方法之一:
- 对数据类型使用帮助程序方法
- 对变量使用帮助程序方法
- 使用
Convert类的方法
稍后,你将在本单元中了解几个用于数据转换的方法示例。
问题:尝试更改值的数据类型是否可能导致信息丢失?
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:int myInt = 3; Console.WriteLine($"int: {myInt}"); decimal myDecimal = myInt; Console.WriteLine($"decimal: {myDecimal}");保存代码文件,然后使用 Visual Studio Code 运行代码。
应会看到以下输出:
int: 3 decimal: 3此示例的关键是下面的代码行:
decimal myDecimal = myInt;由于任何
int值都可以轻松地纳入decimal,因此编译器会执行转换。术语“扩大转换”表示你正在尝试将值从一种可以保留较少信息的数据类型转换为一种可保留较多信息的数据类型。 在这种情况下,存储在
int类型的变量中的值转换为decimal类型的变量时不会丢失信息。如果你了解需要执行扩大转换,则可以依赖于隐式转换。 编译器处理隐式转换。
执行强制转换
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:decimal myDecimal = 3.14m; Console.WriteLine($"decimal: {myDecimal}"); int myInt = (int)myDecimal; Console.WriteLine($"int: {myInt}");若要执行强制转换,请使用强制转换运算符
()将数据类型括起来,然后将其放在要转换的变量旁边(示例:(int)myDecimal)。 对定义的强制转换数据类型 (int) 执行显式转换。保存代码文件,然后使用 Visual Studio Code 运行代码。
应会看到以下输出:
decimal: 3.14 int: 3此示例的关键是下面的代码行:
int myInt = (int)myDecimal;变量
myDecimal保存一个精度在小数点之后的值。 通过添加强制转换指令(int),你告诉编译器 C#,你知道可能发生精度丢失,并且在这种情况下是可以接受的。 你告诉编译器你正在执行有意转换,即显式转换。
定义转换是“扩大转换”还是“收缩转换”
术语“收缩转换”表示你试图将值从一种可保存较多信息的数据类型转换为一种可保存较少信息的数据类型。 在这种情况下,你可能会丢失信息,如精度(即小数点后的位数)。 一个示例是将存储在 decimal 类型的变量中的值转换为 int 类型的变量。 如果打印出这两个值,你可能会注意到出现了信息丢失的情况。
如果你了解需要执行收缩转换,则需要执行强制转换。 强制转换是一种针对 C# 编译器的指令,即你知道可能发生精度丢失,但这种情况可以接受。
如果不确定在转换过程中是否会丢失数据,请编写代码以两种不同的方式执行转换,并观察变化。 开发人员经常编写小型测试来更好地了解行为,如下面的示例所示。
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:decimal myDecimal = 1.23456789m; float myFloat = (float)myDecimal; Console.WriteLine($"Decimal: {myDecimal}"); Console.WriteLine($"Float : {myFloat}");保存代码文件,然后使用 Visual Studio Code 运行代码。
此时会看到与下面类似的输出:
Decimal: 1.23456789 Float : 1.2345679你可以从输出中了解到,将
decimal强制转换为float是收缩转换,因为会丢失精度。
执行数据转换
前面指出,值从一种数据类型更改为另一种数据类型可能会导致运行时异常,应执行数据转换。 对于数据转换,可以使用三种技术:
- 对变量使用帮助程序方法
- 对数据类型使用帮助程序方法
- 使用
Convert类的方法
使用 ToString() 将数字转换为 string
每个数据类型变量都具有 ToString() 方法。 ToString() 方法的作用取决于它在给定类型上的实现方式。 但在大多数基元中,该方法执行扩大转换。 尽管这并不是绝对必要的(因为在大多数情况下你可以依赖于隐式转换),但它可以向其他开发人员传递这样的信息,即你了解正在执行的操作,并且是有意为之的。
下面是使用 ToString() 方法将 int 值显式转换为 string 的简单示例。
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:int first = 5; int second = 7; string message = first.ToString() + second.ToString(); Console.WriteLine(message);保存代码文件,然后使用 Visual Studio Code 运行代码。 运行代码时,输出应显示两个值的串联:
57
使用 Parse() 帮助程序方法将 string 转换为 int
大部分数字数据类型都具有 Parse() 方法,可将字符串转换为给定的数据类型。 在这种情况下,你使用 Parse() 方法将两个字符串转换为 int 值,然后将二者相加。
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:string first = "5"; string second = "7"; int sum = int.Parse(first) + int.Parse(second); Console.WriteLine(sum);保存代码文件,然后使用 Visual Studio Code 运行代码。 在运行代码时,输出应显示两个值的和:
12请花一点时间尝试找出前面的代码示例存在的潜在问题? 如果将
first或second变量设置为无法转换为int的值,会发生什么情况呢? 在运行时引发异常。 C# 编译器和运行时要求你提前计划并防止“非法”转换。 可以通过多种方式缓解运行时异常。缓解此情况最简单的方法是使用
Parse(),它是比TryParse()方法更好的版本。
使用 Convert 类将 string 转换为 int
Convert 类有许多可用于将值从一种类型转换为另一种类型的帮助程序方法。 在下面的代码示例中,你将两个字符串转换为 int 类型的值。
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:string value1 = "5"; string value2 = "7"; int result = Convert.ToInt32(value1) * Convert.ToInt32(value2); Console.WriteLine(result);保存代码文件,然后使用 Visual Studio Code 运行代码。
应会看到以下输出:
35注意
为什么该方法的名称为
ToInt32()? 为什么不是ToInt()?System.Int32是 .NET 类库中的基础数据类型名称,C# 编程语言将其映射到int关键字。 由于Convert类也属于 .NET 类库,因此调用该类时是按其全名(而非按其 C# 名称)进行调用。 通过将数据类型定义为 .NET 类库的一部分,Visual Basic、F#、IronPython 等多种 .NET 语言可以在 .NET 类库中共享相同的数据类型和相同的类。ToInt32()方法具有 19 个重载版本,这让它能够接受几乎所有的数据类型。你在此处将
Convert.ToInt32()方法与字符串一起使用,但应该尽可能使用TryParse()。那么,在什么情况下你应该使用
Convert类?Convert类非常适用于将小数数字转换为整数 (int),因为它按预期方式向上舍入。
比较强制转换和将 decimal 转换为 int
下面的示例演示如果尝试以收缩转换方式将 decimal 强制转换为 int,而不是使用 Convert.ToInt32() 方法将该 decimal 转换为 int,会发生什么情况。
删除或使用行注释运算符
//注释掉上一练习步骤中的代码,并添加以下代码:int value = (int)1.5m; // casting truncates Console.WriteLine(value); int value2 = Convert.ToInt32(1.5m); // converting rounds up Console.WriteLine(value2);保存代码文件,然后使用 Visual Studio Code 运行代码。
应会看到以下输出:
1 2
强制转换截断和转换舍入
强制转换 int value = (int)1.5m; 时,系统会截断浮点数的值,因此结果是 1,这意味着完全忽略小数后的值。 你可以将文本浮点数更改为 1.999m,强制转换的结果也是相同的。
使用 Convert.ToInt32() 进行转换时,文本浮点数值将正确地向上舍入到 2。 如果你将文本值更改为 1.499m,则会向下舍入到 1。
回顾
你了解了有关数据转换和强制转换的多个重要概念:
- 防止执行数据转换时出现运行时错误
- 执行显式强制转换,告诉编译器你了解存在丢失数据的风险
- 执行扩大转换时,依靠编译器执行隐式转换
- 使用
()强制转换运算符和数据类型执行强制转换(例如(int)myDecimal) - 如果要执行收缩转换,但要执行舍入,而不是信息截断,请使用
Convert类