C#入门学习记录(三)C#中的隐式和显示转换

发布于:2025-03-18 ⋅ 阅读:(18) ⋅ 点赞:(0)

C#类型转换:隐式与显式转换的机制与应用

        在C#的强类型体系中,数据类型转换是实现数据交互和算法逻辑的基础操作。当数值类型范围存在包含关系,或对象类型存在继承层次时,系统通过预定义的转换规则实现类型兼容处理。隐式转换(Implicit Conversion)与显式转换(Explicit Conversion)构成了C#类型安全体系的核心机制,二者在编译器行为、数据安全性以及使用场景上存在本质差异。理解其工作原理对避免运行时异常、优化内存使用以及设计健壮的类型系统至关重要。

隐式转换:

        以下是在C#中的一些系统提供的可以直接进行隐式转换的情况:

一、数值类型隐式转换(Numeric Promotions)

        原则是要求数值隐式转换遵循精度不丢失。

// 整型提升
sbyte  a = 100;     short  b = a;    // sbyte → short
short  c = 200;     int    d = c;    // short → int
int    e = 300;     long   f = e;    // int → long

// 浮点提升
float  g = 3.14f;   double h = g;    // float → double

// 混合提升
decimal i = 123.45m; double j = (double)i; // 需要显式转换(decimal→double无隐式)

转换路径
sbyte → short → int → long → float → double
byte → short → ushort → int → uint → long → ulong → float → double
char → ushort → int → uint → long → ulong → float → double

Tips:大的数据范围装小的数据范围,浮点数装整型。

下面是各个类型的值的范围:

二、可空类型(Nullable)隐式转换

        什么是可空类型(Nullable Types)?

        在C#中,​可空类型允许值类型(如 intdoubleDateTime 等)表示为“有值”或“无值”(即 null)。这是为了解决值类型默认不能为 null 的限制,尤其在处理数据库字段、可选参数或未知数据时非常有用。就把值类型变成了一个引用类型。


为什么需要可空类型?
  1. 表示缺失值
    值类型(如 int)默认必须有值,无法直接表示“无数据”状态。例如,数据库中的某个数字字段可能允许为 NULL,此时需要用可空类型来映射这种场景。

  2. 避免魔数(Magic Numbers)​
    不再需要用特殊值(如 -1 或 int.MinValue)表示“无数据”,代码更清晰。

  3. 与数据库交互
    数据库中的 NULL 字段可以直接映射到C#的可空类型变量。

可空类型的语法:

1.利用结构体进行声明:

Nullable<int> number = null;

2.T? 简写形式

int? number = null;   // 等价于 Nullable<int>
double? price = 9.99;
bool? isActive = true;

可空类型的关键特性:

        1. 判断是否有值:HasValue 属性

这个函数返回的是一个bool值

int? age = 25;
if (age.HasValue)
{
    Console.WriteLine($"年龄是:{age.Value}"); // 输出:年龄是:25
}
else
{
    Console.WriteLine("年龄未填写");
}

        2. 安全获取值:GetValueOrDefault()

int? score = null;
int actualScore = score.GetValueOrDefault(); // 无值时返回默认值(0)
Console.WriteLine(actualScore); // 输出:0

int safeScore = score.GetValueOrDefault(60); // 指定默认值
Console.WriteLine(safeScore); // 输出:60

        3. 直接取值:Value 属性(需谨慎)

int? count = 10;
int total = count.Value; // 有值时正常获取

int? invalid = null;
int error = invalid.Value; // 抛出 InvalidOperationException

可空类型的转换与运算:

在C#的可空类型(Nullable<T>)运算中,​任何涉及 null 的算术操作都会导致结果为 null。这是由可空类型的特殊设计规则决定的,具体原理如下:


关键原因:可空类型的"空值传播"特性

当可空类型(如 int?)参与运算时:

  1. 操作数全为有效值 → 执行常规计算

  2. 任一操作数为 null → ​整个表达式结果自动为 null

1. 与非可空类型的转换

int? a = 10;
int b = 20;

int? sum = a + b;    // ✅ 结果为 30(int?)
int? invalid = a + null; // ✅ 结果为 null

2. 合并运算符 ??(提供默认值)

为空的话就返回??右边的值

int? nullableNumber = null;
int actualNumber = nullableNumber ?? 100; // 若为null,返回100

3. 条件访问 ?.

判空处理,如果不为空才会使用这个对象,一般是用于委托,事件

DateTime? birthDate = GetBirthDate();
int? year = birthDate?.Year; // 若birthDate为null,year也为null

好了扯了这么多该回归主线了!

所以int是可以隐式转换为空的,对应其他基础类型也可以这样

int? nullableInt = 42;        // int → int? 隐式
double? nullableDouble = nullableInt; // int? → double?(基础类型支持隐式转换)

三、常量表达式隐式转换

const int max = int.MaxValue;
long bigNumber = max;         // 编译时确定值不会溢出

byte magic = 255;            // 字面量255在byte范围内
// byte invalid = 256;       // 编译错误(超出范围)

        这里说明bigNumber可以装下int 类型的常量,还是和前面是一样的,大的数据类型可以直接装小的数据类型。

四、数组协变(Array Covariance)

        数组协变是C#中一种特殊的类型转换机制,允许将派生类的数组隐式转换为基类的数组。但是!请注意,只能将引用类型的数组转换为基类,而不能将值类型的数组转换,而且!不可逆转。其实还有个泛型协变,我们后面再聊。

        核心规则

​1、仅支持引用类型数组

// 值类型数组不支持协变
int[] ints = { 1, 2 };
// object[] objArr = ints; // ❌ 编译错误

2、协变方向不可逆

object[] objs = new object[2];
// string[] strs = objs; // ❌ 编译错误(逆变不允许)

3、 运行时类型不变

        这个是什么意思呢,我认为是只是换了个父类进行存储,但本身自己是什么还是不会改变。就像你是你老汉生的,但是你始终只是你!

Console.WriteLine(objects.GetType()); // 输出:System.String[]
string[] strings = { "a", "b" };
object[] objects = strings;   // string[] → object[] 隐式转换(协变)
// 仅支持引用类型数组

隐式转换示例:

string[] strings = { "a", "b" };
object[] objects = strings;   // string[] → object[] 隐式转换(协变)
// 仅支持引用类型数组

小结:

            //有符号 long->int->short->sbyte //大范围装小范围
            //无符号 ulong->uint->ushort->byte 同上

            //浮点数 decimal double float
            //decimal这个类型 没有办法用隐式转换的形式 去储存 double和float

            //特殊类型 bool char string
            //他们之间不存在隐式转换

            //1.无符号 不能够 装 有符号
            //有符号的变量不能隐式转换为无符号的变量
            //2.有符号的变量不能装范围和自己相等的或者大的变量 只能装比自己小(范围)的变量

            //3.浮点数可以装任意整数 包括decimal
            //double->float->所有整型
            //4.整数不能隐式装载浮点数

            //bool没有办法和其他类型 相互隐式转换

            //char 没有办法隐式存储 其他类型的变量

            // char 类型可以隐式转换成整型和浮点型(ASCII码)

            //string 无法和其它类型相互转换

显示转换:

大体上有三种方式进行转换

一、括号强制转换(显式类型转换)

        适用场景:已知类型兼容的转换
// 1. 数值类型收缩转换
double d = 3.1415;
int i = (int)d; // 结果为3(截断小数)

// 2. 引用类型向下转型
object obj = "Hello";
string str = (string)obj;

// 3. 可空类型转换
int? nullableInt = 42;
int normalInt = (int)nullableInt;

二、Parse系列方法 将字符串转换为整型

            //作用:把字符串类型 转换为对应的类型
            //语法:变量类型.Parse("字符串")
            //注意:转换的字符串必须是能够转换的

        一、基本数值类型 Parse 方法
1. ​整数类型
string intStr = "123";
int numInt = int.Parse(intStr); // 输出:123

string longStr = "9876543210";
long numLong = long.Parse(longStr);
2. ​浮点数类型
string floatStr = "3.14";
float numFloat = float.Parse(floatStr); // 输出:3.14

string doubleStr = "2.71828";
double numDouble = double.Parse(doubleStr);
3. ​十进制类型

decimal就是能够存数据存的很准确,28位呢

string decimalStr = "12345.67";
decimal numDecimal = decimal.Parse(decimalStr); // ✅ 精确存储为十进制数
Console.WriteLine(numDecimal); // 输出:12345.67(完全一致)
二、布尔类型 Parse
string trueStr = "True";
bool isTrue = bool.Parse(trueStr); // 输出:True

string falseStr = "False";
bool isFalse = bool.Parse(falseStr);
三、日期和时间
1. ​DateTime.Parse
string dateStr = "2025-3-01";
DateTime date = DateTime.Parse(dateStr); // 输出:2023/3/1 0:00:00
2. ​DateTimeOffset.Parse
string offsetStr = "2023-10-01T12:00:00+08:00";
DateTimeOffset offset = DateTimeOffset.Parse(offsetStr); // 带时区信息
四、枚举类型 Enum.Parse 
enum Color { Red, Green, Blue }

string colorStr = "Green";
Color color = (Color)Enum.Parse(typeof(Color), colorStr); // 输出:Color.Green

这里就比较麻烦,先记住吧

五、特殊类型 Parse 方法

1. ​Guid.Parse

string guidStr = "a0a0a0a0-1234-5678-9012-abcdefabcdef";
Guid guid = Guid.Parse(guidStr); // 转换为全局唯一标识符

2. ​TimeSpan.Parse

string timeSpanStr = "12:34:56";
TimeSpan duration = TimeSpan.Parse(timeSpanStr); // 输出:12小时34分56秒
六、其他 Parse 方法

1. ​IPAddress.Parse

string ipStr = "192.168.1.1";
IPAddress ip = IPAddress.Parse(ipStr); // 转换为IP地址对象

2. ​Version.Parse

string versionStr = "2.3.4.5";
Version ver = Version.Parse(versionStr); // 输出:主版本2,次版本3
七、Parse 方法的重载参数
// 标准格式:Parse(string)
int.Parse("123");

// 带格式控制:Parse(string, NumberStyles)
int.Parse("(123)", NumberStyles.AllowParentheses);
关键参数 NumberStyles:
//AllowLeadingWhite:允许前导空格
//AllowTrailingWhite:允许后置空格
//AllowParentheses:允许括号表示负数(如 (123))
//AllowThousands:允许千位分隔符(如 1,234)
//AllowCurrencySymbol:允许货币符号(如 $、¥)

// 允许括号表示负数
int negative = int.Parse("(123)", NumberStyles.AllowParentheses); // 输出:-123

// 允许千位分隔符
int bigNumber = int.Parse("1,234", NumberStyles.AllowThousands); // 输出:1234

// 组合多个格式
decimal money = decimal.Parse("$1,234.56", 
    NumberStyles.AllowCurrencySymbol | NumberStyles.AllowThousands);
// 带格式和文化信息:Parse(string, NumberStyles, IFormatProvider)
double.Parse("$1,234.56", NumberStyles.Currency, CultureInfo.InvariantCulture);
//关键参数 IFormatProvider:
//CultureInfo.InvariantCulture:通用文化规则(常用于数据存储)
//CultureInfo.CurrentCulture:系统当前文化规则
//new CultureInfo("fr-FR"):指定法语文化规则

// 解析美元金额(使用通用文化规则)
double usd = double.Parse("$1,234.56", 
    NumberStyles.Currency, 
    CultureInfo.InvariantCulture); // 输出:1234.56

// 解析法语格式数字(逗号作小数点)
double frenchNumber = double.Parse("1 234,56", 
    NumberStyles.Number, // 允许千位分隔符和小数点
    new CultureInfo("fr-FR")); // 输出:1234.56

// 解析德语负数(括号和逗号分隔符)
int germanNegative = int.Parse("(1.234)", 
    NumberStyles.AllowParentheses | NumberStyles.AllowThousands,
    new CultureInfo("de-DE")); // 输出:-1234
八、Parse 方法的异常处理

所有 Parse 方法在转换失败时会抛出以下异常:

        ​FormatException:输入字符串格式无效        

        ​OverflowException:数值超出目标类型范围

优先使用 TryParse 方法避免异常:

if (int.TryParse("abc", out int result)) {
    // 转换成功
} else {
    Console.WriteLine("转换失败");
}

  TryParse 方法是一种 ​安全且高效的类型转换机制,用于将字符串或其他数据格式尝试转换为目标类型,同时避免因转换失败而抛出异常。

        核心作用:

  1. 避免异常:不抛出 FormatException 或 OverflowException,提升代码健壮性。
  2. 简化逻辑:通过返回布尔值明确指示转换成功与否。
  3. 性能优化:减少 try-catch 块的开销,适合高频调用场景。
方法签名解析
public static bool TryParse(string s, out int result)
  • 输入参数s 是待转换的字符串。
  • 输出参数result 存储转换成功后的值(需用 out 关键字)。
  • 返回值true 表示转换成功,false 表示失败

注意必须使用out进行处理 parse能用的参数 TryParse都能使用

特性 Parse TryParse
异常风险 高(转换失败时抛出) 无异常(返回布尔值)
性能 低(需异常处理开销) 高(无异常处理)
适用场景 确定输入合法的内部逻辑 用户输入、外部数据解析
代码简洁性 需 try-catch 包裹 直接条件判断
  1. 始终优先使用 TryParse:处理用户输入、文件数据或网络请求等不可信来源。
  2. 明确指定文化规则:避免因系统区域设置不同导致解析错误。
  3. 组合格式控制:通过 NumberStyles 精确匹配输入格式。
  4. 验证结果后再使用:检查返回的布尔值后再操作 out 参数。

几乎所有基础类型都提供 TryParse 方法:

  • int.TryParse
  • double.TryParse
  • DateTime.TryParse
  • bool.TryParse
  • Guid.TryParse
  • Enum.TryParse<T>
九、当然你也可以自己定义相关逻辑,然后使用parse
public struct Temperature {
    public double Celsius { get; }

    public static Temperature Parse(string input) {
        if (input.EndsWith("°C")) {
            double value = double.Parse(input.Replace("°C", ""));
            return new Temperature { Celsius = value };
        }
        throw new FormatException("无效的温度格式");
    }
}

Temperature temp = Temperature.Parse("25°C"); // 自定义解析逻辑

三、使用Convert法

            // 作用:更准确的将 各个类型之间进行相互转换
            // 语法:Convert.To目标类型(变量或常量)

Convert的作用:

  Convert 类是 System 命名空间下的静态工具类,用于 ​在不同基础类型之间进行安全转换,相比强制转换 ((type)value) 和 Parse 方法,它具备以下优势:

  1. 处理 null 值:将 null 转换为目标类型的默认值。
  2. 支持更多类型转换:如 bool ↔ intDateTime ↔ string 等。
  3. 兼容 DBNull:常用于数据库操作,处理 DBNull.Value
  4. 格式化控制:提供四舍五入、进制转换等功能。

示例:

1. 基本类型转换
// 字符串 → 整数
int num = Convert.ToInt32("123");       // 123

// 字符串 → 浮点数
double d = Convert.ToDouble("3.14");    // 3.14

// 布尔值 → 整数
int flag = Convert.ToInt32(true);        // 1(false → 0)

// 整数 → 布尔值(非零为 true)
bool isTrue = Convert.ToBoolean(1);      // true
2. 处理 null 和 DBNull
// 转换 null 值
int nullToInt = Convert.ToInt32(null);   // 0(不会抛异常)

// 处理数据库中的 DBNull
object dbValue = DBNull.Value;
int dbNullResult = Convert.ToInt32(dbValue); // 0(而不是抛异常)
3. 四舍五入与精度控制
// 四舍五入为整数
double pi = 3.14159;
int rounded = Convert.ToInt32(pi);       // 3(直接截断)
rounded = Convert.ToInt32(Math.Round(pi)); // 3(推荐手动四舍五入)

// 指定小数位数
decimal money = 123.4567m;
decimal truncated = Math.Round(money, 2); // 123.46
4.进制转换
// 十进制 → 二进制字符串
string binary = Convert.ToString(42, 2); // "101010"

// 二进制字符串 → 十进制
int fromBinary = Convert.ToInt32("101010", 2); // 42

// 十六进制 → 十进制
int fromHex = Convert.ToInt32("FF", 16);       // 255
5. 日期与时间转换
// 字符串 → DateTime
DateTime dt = Convert.ToDateTime("2023-10-01"); 

// DateTime → 字符串
string dateStr = dt.ToString("yyyy-MM-dd");   // "2023-10-01"

// 时间戳(Unix时间)转换
long timestamp = 1696147200; // 2023-10-01 00:00:00 UTC
DateTime fromTimestamp = DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime;

四、类型转字符串方法

1. ToString() 基础方法
// 值类型转字符串
int num = 42;
string s1 = num.ToString(); // "42"

// 引用类型转字符串
DateTime now = DateTime.Now;
string s2 = now.ToString("yyyy-MM-dd"); // "2023-10-01"
2. 字符串插值
double price = 19.99;
string msg = $"价格:{price:C2}"; // "价格:¥19.99"
3. 格式化方法
// 数值格式化
string hex = 255.ToString("X4");       // "00FF"
string percent = 0.85.ToString("P1"); // "85.0%"

// 自定义格式
string custom = 123.456.ToString("#,##0.00"); // "123.46"

总结:

方法 优点 缺点 适用场景
括号强转 执行效率高 类型不安全易抛异常 确定类型兼容的数值转换
Parse 精确控制格式 必须处理异常 严格格式的字符串解析
TryParse 安全无异常 需要out参数 用户输入等不确定数据
Convert 统一接口、支持null 部分转换有隐式规则 数据库交互、通用转换
ToString 灵活性高 可能返回类型名称(如对象未重写) 需要定制格式的输出
字符串插值 代码简洁 无法处理null 快速构建格式化字符串

本文最后需要讲述的是try catch语法

            // 必备部分
            try 
            {
            //希望进行异常捕获的代码块 放到 try 中
            // 若出错 会执行 catch 中的代码 捕获异常
            
            }
            catch 
            {
            
            
            }
            //可选部分
            finally 
            {
            // 最后执行的代码 不管有无出错 都会执行 
            }

示例:

try {
    string content = File.ReadAllText("missing.txt");
} 
catch (FileNotFoundException) {
    Console.WriteLine("文件不存在,请检查路径");
} 
catch (IOException ex) {
    Console.WriteLine($"读取文件失败: {ex.Message}");
} 
catch (Exception ex) {
    Console.WriteLine($"未知错误: {ex.GetType().Name}");
}

这个比较消耗性能,注意不要在反复的循环中调用!

try {
    // 代码
} 
catch (Exception ex) when (ex.Message.Contains("network")) {
    Console.WriteLine("网络相关错误");
} 
catch (Exception ex) when (ex is SocketException || ex is HttpRequestException) {
    Console.WriteLine("通信错误");
}

场景 推荐方法 原因
用户输入验证 TryParse 避免异常开销,直接返回状态码
文件/网络操作 try-catch 必须处理不可预测的 I/O 错误
数学运算检查 预防性条件检查 如 if (divisor != 0) 避免异常

附:异常表格

异常类型(Type) 命名空间 触发场景 示例代码
System.Exception System 所有异常的基类 throw new Exception("通用错误");
System.SystemException System 系统级异常的基类(如 NullReferenceException 通常不直接使用,由其子类继承
System.ArgumentException System 方法接收到无效参数 void SetAge(int age) { if (age < 0) throw new ArgumentException("年龄不能为负"); }
ArgumentNullException System 参数为 null void Print(string text) { if (text == null) throw new ArgumentNullException(nameof(text)); }
ArgumentOutOfRangeException System 参数超出有效范围 List<int> list = new List<int>(); list[0] = 1; // 列表为空,索引越界
FormatException System 字符串格式不符合目标类型要求 int.Parse("12A3"); // 包含非数字字符
OverflowException System 数值运算或转换导致溢出 checked { int max = int.MaxValue; int x = max + 1; }
InvalidCastException System 无效的类型转换 object obj = "hello"; int num = (int)obj;
NullReferenceException System 访问 null 对象的成员 string s = null; int len = s.Length;
IndexOutOfRangeException System 数组/集合索引越界 int[] arr = new int[3]; arr[5] = 10;
DivideByZeroException System 整数除法中除数为零 int x = 5 / 0;
NotImplementedException System 未实现的方法或功能 public override void Execute() { throw new NotImplementedException(); }
NotSupportedException System 调用不支持的操作 ReadOnlyCollection<int>.Add(1); // 只读集合尝试添加元素
InvalidOperationException System 对象状态不适合执行操作 Queue<int> queue = new Queue<int>(); queue.Dequeue(); // 队列为空时出队
KeyNotFoundException System.Collections.Generic 在字典中访问不存在的键 Dictionary<string, int> dict = new Dictionary<string, int>(); int val = dict["missing"];
FileNotFoundException System.IO 访问不存在的文件 File.ReadAllText("missing.txt");
DirectoryNotFoundException System.IO 访问不存在的目录 Directory.GetFiles("C:\\InvalidPath");
IOException System.IO 广义的 I/O 错误(如文件被占用) File.Move("file.txt", "locked_file.txt"); // 目标文件被其他进程锁定
UnauthorizedAccessException System.IO 无权限访问文件/目录 File.WriteAllText("C:\\System\\file.txt", "test"); // 无管理员权限
SqlException System.Data.SqlClient SQL Server 数据库操作错误 sqlConnection.Open(); // 数据库连接失败
TimeoutException System 操作超时 HttpClient().GetAsync(url).Wait(TimeSpan.FromSeconds(1));
TaskCanceledException System.Threading.Tasks 异步任务被取消 await Task.Delay(1000, cancellationToken); // 若 cancellationToken 被取消
TypeInitializationException System 类的静态构造函数抛出异常 class MyClass { static MyClass() { throw new Exception(); } } // 首次访问时触发
OutOfMemoryException System 内存不足(如创建超大数组) byte[] hugeArray = new byte[int.MaxValue];
StackOverflowException System 无限递归导致堆栈溢出(无法捕获)