05-数据类型转化
在 C# 编程中,不同数据类型之间的转换是很常见的操作。理解各种转换机制有助于防止类型不匹配引发的错误,提高代码的安全性和可维护性。
一、隐式转换(Implicit Conversion)
编译器自动完成的类型转换,不会丢失数据。通常是从小范围类型转换为大范围类型。
csharp
int a = 10;
double b = a; // int → double,隐式安全转换
// 整数之间的隐式转换
byte bVal = 100;
short sVal = bVal; // byte → short ✅
int iVal = sVal; // short → int ✅
long lVal = iVal; // int → long ✅
// 浮点之间的隐式转换
float fVal = 3.14f;
double dVal = fVal; // float → double ✅
// int 到 float/double 的隐式转换(可能丢失精度但允许)
int big = 123456789;
float f = big; // 隐式转换,大 int 转 float 可能丢失精度隐式转换对照表
| 源类型 | 目标类型 |
|---|---|
byte / sbyte | short, int, long, float, double, decimal |
short / ushort | int, long, float, double, decimal |
int / uint | long, float, double, decimal |
long / ulong | float, double, decimal |
float | double |
| 派生类 | 基类 |
| 实现了接口的类型 | 对应接口 |
二、显式转换(Explicit Conversion / 强制转换)
必须使用强制转换符号 (类型),可能丢失数据。通常是从大范围类型转换为小范围类型。
csharp
double pi = 3.14159;
int intPi = (int)pi; // double → int,小数部分被截断
Console.WriteLine(intPi); // 3
// 大范围整数转小范围
long large = 123456789L;
int small = (int)large; // long → int,可能溢出
// 浮点转整数
float f = 3.9f;
int i = (int)f; // 截断小数,结果是 3(不是四舍五入!)
// 精确四舍五入的方案
int rounded = (int)Math.Round(f); // 4(四舍五入)显式转换的注意事项
csharp
// 1. 小数截断不是四舍五入
Console.WriteLine((int)3.9); // 3(截断)
Console.WriteLine((int)-3.9); // -3(向零取整)
// 2. 超出范围时可能溢出(默认不检查)
int max = int.MaxValue; // 2,147,483,647
int overflowed = max + 1; // 不报错,但结果不对(环绕为 -2147483648)
// 3. 使用 checked 检查溢出
checked
{
// int result = int.MaxValue + 1; // ❌ 运行时抛出 OverflowException
}三、Convert 类转换
System.Convert 类提供了在不同基本类型之间转换的方法,失败时抛出异常。
csharp
// 字符串转数字
string str = "123";
int num = Convert.ToInt32(str); // "123" → 123
double d = Convert.ToDouble(str); // "123" → 123.0
// 类型互转
int i = 65;
char c = Convert.ToChar(i); // 65 → 'A'
string s = Convert.ToString(i); // 65 → "65"
bool b = Convert.ToBoolean(1); // 1 → true
// double 转 int(会四舍五入,不是截断)
double pi = 3.14159;
int rounded = Convert.ToInt32(pi); // 3(四舍五入)
int truncated = (int)pi; // 3(截断)
// 注意:当小数为 .5 时,Convert 采用"银行家舍入"(四舍六入五成双)
Console.WriteLine(Convert.ToInt32(3.5)); // 4(3.5 四舍五入)
Console.WriteLine(Convert.ToInt32(4.5)); // 4(4.5 被舍去!因为 4 是偶数)Convert 与强制转换的区别
| 对比项 | (类型) 强制转换 | Convert 类 |
|---|---|---|
| 失败时 | 编译成功,运行时可能异常 | 抛出 FormatException / InvalidCastException |
| 小数处理 | 截断 | 四舍五入(银行家舍入) |
| 字符串转换 | 不支持直接转数字 | 支持 |
| 类型兼容性 | 要求类型相关 | 更灵活 |
| null 处理 | 值类型转 null 会异常 | 返回默认值或异常 |
四、Parse 和 TryParse
专门用于字符串 → 数值类型的转换。
Parse —— 失败抛异常
csharp
int num = int.Parse("123"); // ✅ 成功
double d = double.Parse("3.14"); // ✅ 成功
DateTime dt = DateTime.Parse("2024-10-01"); // ✅ 成功
// int bad = int.Parse("abc"); // ❌ 抛出 FormatExceptionTryParse —— 失败返回 false(推荐)
csharp
string input = "123";
if (int.TryParse(input, out int result))
{
Console.WriteLine($"转换成功:{result}");
}
else
{
Console.WriteLine("转换失败");
}
// TryParse 的简化模式
if (int.TryParse(Console.ReadLine(), out int age))
{
Console.WriteLine($"你输入的年龄是:{age}");
}
else
{
Console.WriteLine("无效的数字");
}Parse 和 TryParse 支持的类型
| 方法 | 说明 |
|---|---|
int.Parse() / int.TryParse() | 字符串 → int |
long.Parse() / long.TryParse() | 字符串 → long |
double.Parse() / double.TryParse() | 字符串 → double |
decimal.Parse() / decimal.TryParse() | 字符串 → decimal |
bool.Parse() / bool.TryParse() | 字符串 → bool |
DateTime.Parse() / DateTime.TryParse() | 字符串 → DateTime |
Enum.Parse<>() / Enum.TryParse<>() | 字符串 → 枚举 |
Parse vs TryParse 选择
| 场景 | 推荐 |
|---|---|
| 输入确定合法 | Parse |
| 用户输入(不确定) | TryParse |
| 配置文件解析 | TryParse |
| 性能要求高 | TryParse(避免异常开销) |
五、is 和 as 运算符
用于引用类型之间的类型检查和转换。
is 运算符——检查类型
csharp
object obj = "Hello";
// 判断类型
bool isString = obj is string; // True
bool isInt = obj is int; // False
// C# 7.0+ 模式匹配:检查 + 转换
if (obj is string text)
{
Console.WriteLine($"字符串长度:{text.Length}");
}
// 还可以检查并取反
if (obj is not int)
{
Console.WriteLine("不是 int 类型");
}
// switch 模式匹配
string Describe(object obj) => obj switch
{
int i => $"整数:{i}",
string s => $"字符串:{s}",
null => "null",
_ => $"其他:{obj.GetType().Name}"
};as 运算符——安全转换
csharp
object obj = "Hello";
// as 转换:成功返回目标类型,失败返回 null
string? str = obj as string; // "Hello"
int? num = obj as int?; // null(int 是值类型,需用 int?)
if (str != null)
{
Console.WriteLine(str.Length); // 安全使用
}
// as 只能用于引用类型和可空值类型
// int i = obj as int; // ❌ 编译错误:int 不能为 nullis / as / 强制转换对比
| 方式 | 失败行为 | 适用类型 | 推荐场景 |
|---|---|---|---|
is 检查 | 返回 false | 所有类型 | 只需要判断类型 |
as 转换 | 返回 null | 引用类型 + 可空值类型 | 安全的类型转换 |
(T)obj 强制转换 | 抛出异常 | 所有类型 | 确定类型一定正确时 |
六、装箱和拆箱(特殊转换)
装箱(Boxing)将值类型转换为 object 或接口类型,拆箱(Unboxing)将装箱后的对象还原为原始值类型。两者都有显著性能开销。
详细内容已移至独立文档:06-装箱拆箱
csharp
int value = 42;
object boxed = value; // 装箱:栈 → 堆
int unboxed = (int)boxed; // 拆箱:堆 → 栈七、checked 和 unchecked(溢出检查)
csharp
// unchecked:允许溢出(默认)
int x = int.MaxValue;
int y = unchecked(x + 1); // 结果:-2147483648(绕回)
// checked:溢出时抛异常
try
{
int z = checked(x + 1); // 抛出 OverflowException
}
catch (OverflowException)
{
Console.WriteLine("溢出!");
}
// 在项目设置中默认启用 checked
// 或在代码块级别启用
checked
{
int result = x + 1; // 溢出时抛异常
}八、类型转换方法选择指南
| 需求场景 | 推荐方法 | 示例 |
|---|---|---|
| int → long(扩大) | 隐式转换 | long l = i; |
| double → int(缩小) | 强制转换 | int i = (int)d; |
| string → int | int.Parse() / TryParse() | int.Parse("123") |
| object → string | as 或强制转换 | obj as string |
| 任意类型 → string | .ToString() | age.ToString() |
| 数值 → 其他数值 | Convert 类 | Convert.ToInt32(d) |
| 枚举 ↔ int | 强制转换 | (int)Season.Summer |
| 检查类型 | is 运算符 | obj is string |
核心知识点总结
转换方式速查
| 转换方式 | 关键字/方法 | 是否安全 | 是否丢失数据 |
|---|---|---|---|
| 隐式转换 | 自动 | 安全 | 否(小→大) |
| 显式转换 | (类型) | 可能异常 | 可能 |
| Convert 类 | Convert.ToXxx() | 可能异常 | 可能 |
| Parse | xxx.Parse() | 可能异常 | 否(格式问题) |
| TryParse | xxx.TryParse() | 安全(返回 bool) | 否 |
| as 转换 | obj as T | 安全(返回 null) | 否 |
| is 检查 | obj is T | 安全 | 不转换 |
注意事项
- 强制转换截断小数——
(int)3.9结果是 3,不是 4 - Convert 会四舍五入——
Convert.ToInt32(3.9)结果是 4 - TryParse 比 Parse 安全——用户输入优先用 TryParse
- is + 模式匹配是最优雅的类型检查方式
- as 失败返回 null——不会抛异常,比强制转换安全
- checked 防止静默溢出——需要时使用
checked关键字 - 装箱拆箱影响性能——大量循环中应避免,使用泛型集合
- 拆箱类型必须精确匹配——box 了什么类型,unbox 也要什么类型


