类型转换、强制转换和装箱

小窍门

开发软件的新手? 首先开始 学习入门 教程。 在进行转换选择之前,先练习基本类型。

是否在其他语言中有经验? C# 转换的工作方式与大多数静态类型的语言类似:扩大转换是隐式转换,收缩转换需要显式强制转换,而解析文本在面向用户的代码中应首选 TryParse。

编写 C# 代码时,通常会将值从一种类型转换为另一种类型。 例如,你可以将 int 转换为 long,读取文本并将其转换为数字,或将基类型转型为派生类型。

了解关键术语:

  • 转换是将值从一种类型更改为另一种类型的过程。
  • 强制转换是用于使用 (int)value 等括号编写的转换的显式语法。
  • 隐式转换是编译器可以保证安全时自动发生的转换。
  • 显式强制转换是采用代码编写的转换,指示转换可能会丢失信息或失败。

将转换样式与情况匹配:

  • 当编译器可以保证安全性时,会自动进行隐式转换,无需语法。
  • 当数据可能丢失或转换可能失败时,需要显式转换。
  • 在需要可能不会成功的安全引用类型转换时,会应用模式匹配或 as。
  • 当源值为文本时,分析 API 适用。

隐式和显式数值转换

隐式转换始终成功。 显式转换可能会失败或丢失信息。

int itemCount = 42;
long widened = itemCount; // Implicit conversion.

double average = 19.75;
int truncated = (int)average; // Explicit cast.

Console.WriteLine($"widened: {widened}, truncated: {truncated}");

显式强制转换告知读取器转换可能会丢失信息。 在示例中, double 值在转换为 int时被截断。

有关完整转换表,请参阅 内置数值转换。

转换引用

类 是引用类型。 对它们进行强制转换不会复制数据。 它们更改了查看同一对象的方式。

某些引用转换是隐式的。 编译器保证它们是安全的。 三种情况始终生成隐式引用转换:将派生类实例分配给基类变量(派生类型是基类型的子类型),将引用类型实例分配给类型实现的接口的变量,并将任何引用类型分配给变量 object 。

Mammal otter = new() { Name = "River otter" };
Animal animal = otter;           // Derived to base class: always safe.
INamed named = otter;            // Reference type to implemented interface: always safe.
object obj = otter;              // Any reference type to object: always safe.

Console.WriteLine(named.Name);

从基类型返回到派生类型的另一个方向需要显式检查,因为对象实际上可能不是所需的派生类型。 首选模式匹配,以便测试和赋值同时进行。

Animal knownAnimal = new Mammal { Name = "River otter" };

if (knownAnimal is Mammal mammal)
{
    Console.WriteLine($"Pattern match succeeded: {mammal.Name}");
}

Animal unknownAnimal = new Reptile();
Console.WriteLine($"Can treat as mammal: {unknownAnimal is Mammal}");

模式匹配将成功的强制转换变量保持在最小的有效范围内,从而提高可读性。

如果需要可为 null 的结果而不是条件分支,请使用 as:

object boxedMammal = new Mammal { Name = "Sea lion" };
Mammal? maybeMammal = boxedMammal as Mammal;
Console.WriteLine(maybeMammal is null ? "Not a mammal" : maybeMammal.Name);

object boxedReptile = new Reptile();
Mammal? noMammal = boxedReptile as Mammal;
Console.WriteLine(noMammal is null ? "Safe null result" : noMammal.Name);

仅对引用类型和可为 null 的值类型使用 as 。 转换失败时返回 null 。

了解装箱和拆箱

装箱可以将结构或其他值类型转换为 object 或实现的接口类型。 取消装箱将从该对象引用中提取值类型。

int temperature = 72;
object boxedTemperature = temperature; // Boxing.
int unboxedTemperature = (int)boxedTemperature; // Unboxing.

Packet packet = new(7);
ILabelled labelledPacket = packet; // Boxing through an interface reference.

Console.WriteLine($"Unboxed: {unboxedTemperature}, Label: {labelledPacket.Label}");

装箱会在托管堆上分配内存,而取消装箱需要类型检查。 在热路径中,避免使用不必要的装箱,因为这会增加分配和额外工作。

使用 Parse 和 TryParse 分析文本

转换用户输入或文件内容时,从 TryParse 开始。 这可避免预期无效输入导致的异常,并使故障处理变为显式进行。 所有分析 API 都从源字符串创建新的对象或值类型实例;它们不会修改源。

string textValue = "512";
int parsed = int.Parse(textValue);

string userInput = "12x";
bool parsedSuccessfully = int.TryParse(userInput, out int safeValue);

Console.WriteLine($"parsed: {parsed}");
Console.WriteLine(parsedSuccessfully ? $"safe value: {safeValue}" : "Input is not a valid number.");

当保证输入有效(如受控测试数据)时使用 Parse 。 请使用 TryParse 表示用户输入、网络有效负载和文件数据。

核心转换 API

在日常代码中最常使用这些 API:

如需了解高级转换行为和所有重载,请查阅特定目标类型的 API 参考。

另见