C# 中的类型转换
最后修改于 2025 年 6 月 3 日
C# 中的类型转换是指将一个数据类型的值转换为另一个数据类型的过程。本教程探讨了隐式转换和显式转换、装箱/拆箱、溢出处理、字符串转换以及表达式中的类型提升,并提供实际示例来说明每个概念。
C# 支持两种主要的转换类型:隐式转换(自动且安全)和显式转换(需要强制转换,可能导致数据丢失)。类型提升是隐式转换的一个子集,发生在表达式中,以确保类型之间的兼容性。理解这些机制对于编写健壮的 C# 代码并避免常见的陷阱(如溢出或精度损失)至关重要。
类型转换比较表
下表总结了 C# 中类型转换的关键特性
| 功能 | C# 行为 |
|---|---|
| 隐式转换 | 自动,无数据丢失(例如,int → long,long → double) |
| 显式转换 | 需要强制转换,可能丢失数据(例如,double → int) |
| 装箱 | 将值类型转换为对象或接口(隐式) |
| 拆箱 | 从对象中提取值类型(显式,可能引发 InvalidCastException) |
| 溢出控制 | 用于算术运算的 checked/unchecked 上下文 |
| 字符串转换 | 将字符串解析为其他类型的 Convert, Parse, TryParse |
| 类型提升 | 表达式中自动提升为更宽的类型(例如,int + double → double) |
此表提供了 C# 类型转换功能的快速概述,重点介绍了隐式转换和显式转换、装箱/拆箱、溢出控制、字符串转换方法以及表达式中的类型提升之间的差异。理解这些特性对于有效的 C# 编程至关重要。
数值类型提升层次结构
下表概述了 C# 表达式的数值类型提升规则
| 操作 | C# 结果类型 | 注意 |
|---|---|---|
| byte + byte | int | byte 提升为 int |
| short + short | int | short 提升为 int |
| char + char | int | char 提升为 int |
| int + long | long | int 提升为 long |
| long + float | float | long 提升为 float |
| float + double | double | float 提升为 double |
在涉及 byte、short 或 char 的表达式中,C# 将较小的类型提升为 int。对于混合类型运算,结果将提升为更宽的类型,以防止数据丢失。
提升层次结构演示
此示例通过在不同的数值类型之间执行各种算术运算来演示 C# 中的提升层次结构。每个运算的结果都存储在一个对象数组中,并打印每个结果的类型和值,以显示 C# 如何在混合表达式中提升类型。
byte b = 10;
short s = 20;
char c = 'A';
int i = 30;
long l = 100L;
float f = 5.5f;
double d = 2.2;
// Demonstrating promotion hierarchy
object[] results = [
b + b, // byte + byte → int
s + s, // short + short → int
c + c, // char + char → int
i + i, // int + int → int
l + l, // long + long → long
f + f, // float + float → float
d + d, // double + double → double
b + s, // byte + short → int
s + c, // short + char → int
i + l, // int + long → long
l + f, // long + float → float
f + d // float + double → double
];
foreach (var r in results)
{
Console.WriteLine(r.GetType() + ": " + r);
}
此代码展示了 C# 如何在算术表达式中自动将操作数提升为适当的类型。输出显示每个运算的结果类型和值,说明了实践中的提升规则。
发生类型转换的上下文
C# 中的类型转换发生在各种上下文中,如下总结
| 上下文 | C# 行为 | 示例 |
|---|---|---|
| 赋值 | 隐式用于加宽,显式用于变窄 | int i = 100; long l = i; |
| 方法参数 | 隐式提升以匹配参数类型 | 使用 Print(5); 调用 void Print(long x); |
| 算术运算 | 提升到涉及的最宽类型 | byte + short → int |
| 数组初始化 | 隐式转换为数组的类型 | double[] arr = {1, 2, 3.5}; |
| 条件运算符 | 提升到通用类型 | (true) ? 5 : 5.5 → double |
| 复合赋值 | 提升右侧,强制转换回左侧类型 | byte b = 1; b += 2; |
| 返回语句 | 提升到方法的返回类型 | return 5; 在 double 方法中 |
这些上下文说明了 C# 如何根据情况自动或显式地处理类型转换。理解这些上下文有助于开发人员编写避免常见陷阱(如溢出或精度损失)的代码。
数组初始化中的类型转换
C# 在初始化数组时自动执行类型转换,提升或转换元素以匹配声明的数组类型。这允许您在初始化器中混合兼容的类型,只要每个值都可以隐式转换为数组的元素类型即可。如果值不能隐式转换,则需要显式强制转换。
int[] intArr = [1, 2, 3];
double[] doubleArr = [1, 2, 3.5];
object[] objArr = [1, "hello", 3.14];
Console.WriteLine(string.Join(", ", intArr));
Console.WriteLine(string.Join(", ", doubleArr));
Console.WriteLine(string.Join(", ", objArr));
在此示例中,double[] 数组使用 int 和 double 值初始化;int 值会自动提升为 double。object[] 数组可以容纳任何类型,因此所有元素都转换为 object。
隐式转换
当较小类型的值分配给较大类型的变量时,会自动发生隐式转换,确保不会丢失数据。这些转换遵循 C# 的类型层次结构并且是安全的。
byte b = 100;
short s = b; // byte → short
int i = s; // short → int
long l = i; // int → long
float f = l; // long → float
double d = f; // float → double
Console.WriteLine($@"byte: {b}, short: {s}, int: {i}
long: {l}, float: {f}, double:{d}");
此示例演示了 C# 中的隐式转换层次结构:byte → short → int → long → float → double。每次赋值都会自动执行,因为目标类型可以完全表示源值而不会丢失。例如,一个字节(8 位)可以安全地放入一个短整型(16 位),依此类推。
显式转换(强制转换)
从较大类型转换为较小类型时,需要显式转换或强制转换,这可能会导致数据丢失或截断。
double d = 123.456;
int i = (int)d; // Explicit cast: truncates decimal part
byte b = (byte)i; // Explicit cast: may lose data if i > 255
Console.WriteLine($"double: {d}, int: {i}, byte: {b}");
在此示例中,将 double 强制转换为 int 会截断小数部分,结果为 123。如果整数超出字节的范围 (0-255),则将 int 强制转换为 byte 可能会导致数据丢失。
装箱和拆箱
装箱将值类型转换为引用类型(例如,object 或接口),而拆箱从引用类型中提取值类型。拆箱需要显式强制转换,如果类型不匹配,可能会引发 InvalidCastException。
int num = 42;
object boxed = num; // Boxing: int to object
int unboxed = (int)boxed; // Unboxing: object to int
Console.WriteLine($"Boxed type: {boxed.GetType().Name}, Value: {boxed}");
Console.WriteLine($"Unboxed value: {unboxed}");
try
{
object str = "123";
int wrong = (int)str; // Throws InvalidCastException
}
catch (InvalidCastException e)
{
Console.WriteLine($"Error: {e.Message}");
}
装箱将整数 42 包装到 object 中,拆箱将其检索出来。此示例显示将字符串拆箱为整数会引发 InvalidCastException。输出包括装箱的类型和值、拆箱的值以及错误消息。
Checked 和 Unchecked 上下文
C# 提供 checked 和 unchecked 上下文来控制算术溢出行为。在 checked 模式下,溢出会引发 OverflowException。在 unchecked 模式下(默认),溢出会回绕。
try
{
checked
{
int max = int.MaxValue;
int result = max + 1; // Throws OverflowException
Console.WriteLine(result);
}
}
catch (OverflowException e)
{
Console.WriteLine($"Checked overflow: {e.Message}");
}
unchecked
{
int max = int.MaxValue;
int result = max + 1; // Wraps around
Console.WriteLine($"Unchecked result: {result}");
}
在 checked 块中,将 1 添加到 int.MaxValue 会引发 OverflowException。在 unchecked 块中,结果回绕到 int.MinValue。此功能允许开发人员在关键计算中强制执行严格的溢出检查。输出显示异常消息和回绕的结果。
方法参数中的类型转换
C# 在可能的情况下自动提升或转换参数以匹配方法的参数类型。这允许您将较小或兼容类型的值传递给期望更宽类型的方法,而无需显式强制转换。但是,变窄转换(从更宽类型到更小类型)需要显式强制转换,并且可能导致数据丢失。
void PrintVal(long value)
{
Console.WriteLine($"Long value: {value}");
}
int i = 42;
byte b = 10;
PrintVal(i); // int promoted to long
PrintVal(b); // byte promoted to long
double d = 123.45;
// PrintVal(d); // Error: cannot implicitly convert double to long
PrintVal((long)d); // Explicit cast required
在此示例中,PrintVal 接受一个 long 参数。允许传递 int 或 byte,因为它们会隐式提升为 long。传递 double 需要显式强制转换,因为它是一种变窄转换。
字符串转换方法
C# 提供了 Convert、Parse 和 TryParse 等方法,用于将字符串转换为数值类型。Parse 在输入无效时引发异常,而 TryParse 返回一个布尔值,指示是否成功,这使其对于用户输入更安全。
string validStr = "123";
string invalidStr = "abc";
// Using Parse
try
{
int num = int.Parse(validStr);
Console.WriteLine($"Parsed number: {num}");
int error = int.Parse(invalidStr); // Throws FormatException
}
catch (FormatException e)
{
Console.WriteLine($"Parse error: {e.Message}");
}
// Using TryParse
bool success = int.TryParse(validStr, out int result1);
Console.WriteLine($"TryParse valid: Success={success}, Result={result1}");
bool failure = int.TryParse(invalidStr, out int result2);
Console.WriteLine($"TryParse invalid: Success={failure}, Result={result2}");
Parse 将 "123" 转换为 123,但对于 "abc" 引发 FormatException。TryParse 安全地处理这两种情况,对于有效输入返回 true,对于无效输入返回 false,结果存储在 out 参数中。输出显示成功和失败的转换。
表达式中的类型提升
C# 在表达式中将类型提升到涉及的最宽类型,以确保不会丢失数据。例如,byte、short 和 char 在算术运算中提升为 int。
byte b = 10;
short s = 20;
char c = 'A'; // ASCII 65
int i = 30;
long l = 100L;
float f = 5.5f;
// Promotions to int
int result1 = b + s + c; // byte + short + char → int
// Promotions to wider types
long result2 = i + l; // int + long → long
float result3 = l + f; // long + float → float
double result4 = f + i; // float + int → double (via float)
Console.WriteLine($"Result1 (int): {result1}");
Console.WriteLine($"Result2 (long): {result2}");
Console.WriteLine($"Result3 (float): {result3}");
Console.WriteLine($"Result4 (double): {result4}");
在此示例中,byte、short 和 char 提升为 int 进行加法运算,产生 95 (10 + 20 + 65)。其他运算根据最宽的类型提升为 long、float 或 double。输出显示结果及其类型。
如果不理解隐式转换规则,可能会导致意外结果。
byte b1 = 100;
byte b2 = 100;
// byte sum = b1 + b2; // Implicit conversion to int
byte sum = (byte)(b1 + b2);
Console.WriteLine($"Sum of {b1} and {b2} is {sum}");
Console.WriteLine(sum.GetType());
Console.WriteLine(Byte.MaxValue);
Console.WriteLine(Byte.MinValue);
例如,由于隐式转换规则,添加两个 byte 值会导致 int。要将结果存储回 byte 中,您必须显式地强制转换它,如示例所示。
来源
掌握 C# 中的类型转换和提升对于编写高效且无错误的代码至关重要。这些机制会影响赋值、表达式和方法调用,并且理解它们有助于防止诸如溢出或精度损失之类的细微错误。
作者
列出所有 C# 教程。