>> >> >> Reference << << << <<<<<<Ref>>>>>>
Data Type and Convert
Modified: 2025-06-01 | Author:ljf12825

C#中的数据类型可以分为值类型、引用类型、指针类型

值类型(Value Types)

变量直接包含其值,而不是引用

它们通常分配在栈上(也可能在堆上,取决于上下文)

特点

常见的值类型

  1. 内置基本类型(C#关键字)
  1. 枚举类型(enum)
enum Day { Sunday, Monday, Tuesday }
  1. 结构体类型(struct)
struct Point
{
	public int X;
	public int Y;
}
  1. Nullable<T>(可空值类型) 可空值类型是C#中用于给值类型添加null表示能力的一种语法和语义扩展,主要解决“值类型不能为null”的限制 很多情况下,值类型需要一个“未赋值”或“无值”的状态,例如\

定义方式(两种等价)

int? x = null; // 常用写法(语法糖)
Nullale<int> x = null; // 等价写法(本质)

可以用在任何值类型上

bool? b;
float? f;
decimal? money;
DateTime? birthday;

Nullable的本质
编译器背后的原理是这个结构体

public struct Nullable<T> where T : struct
{
  public T Value { get; }
  public bool HasValue { get; }
}

所以int?本质上是Nullable<int>,包含两部分

HasValue

int? a = null;
Console.WriteLine(a.HasValue); // false

Value

int? b = 10;
if (b.HasValue)
	Console.WriteLine(b.value); // 10

如果访问null值的.Value,会抛出异常InvalidOperationException

空合并运算符??

int? age = null;
int realAge = age ?? 18; // 如果age是null,则使用18

可空值运算

int? a = null;
int? b = 5;
int? c = a + b; // c == null, 如果任意一项是null,结果是null

判断一个类型是否是值类型
可以使用typeof(T).IsValueType

Console.WriteLine(typeof(int).IsValueType); // True
Console.WriteLine(typeof(stirng).IsValueType); // False

判断一个类型的大小
可以使用sizeof(T)

获取类型名
this.GetType().Name, typeof(T).Name

System.ValueType

System.ValueType是.NET类型系统中的一个基础类,位于System命名空间中,它是所有值类型(value types)的基类,其存在意义主要是为了让值类型可以作为对象(object)使用

System.ValueType是值类型的抽象父类,本身是一个引用类型,它继承自System.Object,但专门为值类型提供了一些特性

所有用struct关键字定义的类型,都会自动继承System.ValueType,这与class自动继承System.Object类似

ValueType的作用

  1. 实现值类型和引用类型的桥梁
  1. 重写了Object的方法 ValueType重写了EqualsGetHashCodeToStirng方法,使得结构体能够正确地比较和显示内容
struct Point
{
  public int X;
  public int Y;
}
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = new Point { X = 1, Y = 2 };

Console.WriteLine(p1.Equals(p2)); // 比较字段而不是引用

为所有值类型统一提供对System.Object的方法的“默认特化实现”

而这些对于值类型来说是不合理的

在内部,System.ValueType重写了这些方法

  1. Equals(object obj)
  1. GetHashCode()
  1. ToString()

虽然ValueType做了字段比较,但性能不算高,因为使用的是反射(尤其在Equals()中)

所以在性能敏感场景(如大量结构体比较)推荐手动重写

引用类型(Reference Types)

在C#中,引用类型表示变量中存储的是对象的引用地址,而不是对象本身。对象的实际数据存储在托管堆中

特性值类型(Value Type)引用类型(Reference Type)
存储位置栈或嵌入到对象中堆(实际对象),栈中存储引用
是否复制对象是,按值复制否,复制的是引用
可为 null否(除非是 Nullable<T>
继承不能继承(只能继承自 System.ValueType可继承
默认比较行为比较内容比较引用地址(除非重写 Equals
类型种类说明示例
class类,是最常用的引用类型class Person {}
interface接口,也是引用类型interface IMovable {}
delegate委托,用于函数对象delegate void Action();
object所有引用类型的基类object obj = new Person();
string字符串是特殊的引用类型(不可变string s = "hello";

内存结构:引用类型的生命周期

变量声明:
Person p = new Person();

内存布局:
+--------------------+
| 栈内存             |
|--------------------|
| p (引用地址) ------|------+
+--------------------+      |
                            |
+--------------------+      |
| 堆内存             | <----+
|--------------------|
| Person 实例        |
| name = "Jeff"      |
+--------------------+

栈中保存变量p的引用地址,堆中存储实际的Person对象。当引用失效,GC会自动清理对象

深拷贝与浅拷贝

class Person { public string Name; }

Person p1 = new Person { Name = "Alice" };
Person p2 = p1; // p2引用同一个对象

p2.Name = "Bob";
Console.WriteLine(p1.Name); // Bob

两个变量指向同一块堆内存,修改其中一个变量的字段,另一个变量也会反映这个修改

public Person DeepCopy() => return new Person { Name = string.Copy(this.Name) };

或者使用序列化技术实现深拷贝(如JsonSerializer/BinaryFormatter

引用类型的默认行为

方法默认行为
Equals()比较引用地址(可重写)
GetHashCode()与引用相关(可重写)
== 运算符比较地址(除非被重载,例如 string

引用类型与GC

引用类型对象位于托管堆中,由CLR的GC管理内存

GC回收条件

C#的析构函数不确定何时调用,由GC控制

为什么需要引用对象

有很多实际需求无法通过值类型(拷贝)来完成,必须依赖对象共享、动态行为、抽象能力、复杂结构等特性,而这些特性只能由引用对象提供

值类型效率高但灵活性低,引用类型牺牲了一些效率,换来了更强大的表达力与建模能力

目的引用对象的作用
共享数据多个变量/系统使用同一个对象
建模复杂实体对象可以拥有状态、行为、继承层次
动态性与多态性支持虚方法、接口、动态派发等
避免内存复制对象很大时,拷贝成本高,引用更轻便
支持链式结构如图结构、树、图、链表等
GC自动管理堆分配+GC管理,程序更安全稳定

为什么引用类型内容在堆上

  1. 引用类型大小不可预知 栈是一块连续的内存区域,要求每个变量在编译期就确定大小
    而引用类型是:

所以,引用类型的大小通常在运行时才知道,无法放在栈上

  1. 栈的生命周期与方法调用同步,太短 栈上的变量在方法返回后立即销毁,如果对象存在于栈上,那一旦函数返回了,对象也就不存在了
    而引用类型经常在方法之外继续使用(比如:作为成员变量、事件回调、全局数据等),必须在方法栈帧之外生存

  2. 需要支持多个引用指向同一个对象 如果把对象放在栈中,那么指向该对象的引用就可能失效,为了保证引用安全和共享性,引用类型的数据必须放在一个不会随函数退出而消失的地方:也就是托管堆

  3. 支持GC 值类型(在栈上)是自动释放的,编译器控制生命周期
    引用类型(在堆上)需要动态管理
    所以CLR引入了GC,自动清理没有引用的堆对象

Object

在C#中,Object类(也叫System.Object)是一切类型的根基,是CLR类型系统的根基(Root Class),无论是值类型、引用类型,最终都从Object直接或间接继承而来

namespace System
{
  public class Object
  {
    public Object();
	public virtual bool Equals(object obj);
	public static bool Equals(object objA, object objB);
	public static bool ReferenceEquals(object objA, object objB);
	public virtual int GetHashCode();
	public virtual string ToString();
	public virtual void Finalize();
	public Type GetType();
  }
}
  1. Equals() 用于判断两个对象是否“值相等”(默认比较地址),如果需要比较“值”,需要重写Equals()

  2. ReferenceEquals() 比较两个引用是否指向完全相同的对象

  1. GetHashCode() 返回一个整数,用于表示对象的哈希值
    默认行为是:

如果自定义类并重写Equals(),必须也重写GetHashCode(),以保证在哈希表中工作正确

  1. ToString() 返回对象的字符串表示,默认是类名

  2. GetType() 获取当前实例的运行时类型(返回System.Type对象),是实现反射的基础

  3. Finalize() 析构函数的底层形式

~MyClass() { ... } // 实际上编译成 override Finalize()

ObjectobjectSystem.Object

在C#中,object是一个语言关键字,它等价于System.Object,而Object通常是指引入了System命名空间后对System.Object的简写引用名

它们其实都代表同一个.NET类型:System.Object,区别仅仅是上下文不同

名称类型含义示例
System.Object完整类型名BCL中的真正类型,定义在 mscorlib.dllSystem.Object o = new System.Object();
Object类型别名(类名)当引入了 using System; 后的简写写法Object o = new Object();
objectC# 关键字编译器关键字,等价于 System.Object,就像int等价于System.Int32一样object o = new object();

编译器角度 这三种形式在IL中都是一样的

object o = new object();
Object o2 = new Object();
System.Object o3 = new System.Object();

编译之后的IL代码中,它们都会创建一个System.Object实例

用法推荐写法原因
普通开发object符合 C# 语言习惯,简洁
底层开发(如反射、Interop)System.Object更明确,避免命名冲突
语法糖场景objectint, string 等一致

一般写object即可,它们三个是一个东西

String

String是C#中最常用也是最重要的类型之一,它不仅仅是字符串的容器,更体现了C#语言设计的不可变性、托管安全、优化机制等诸多理念

string是C#关键字,对应.NET中的System.String类型,它是一个不可变(immutable)的引用类型,用于表示Unicode字符

特性描述
类型名System.String
C# 关键字string
所属程序集mscorlib.dll
类型分类引用类型,但行为类似值类型(不可变 + 值语义)
存储内容UTF-16 编码的 Unicode 字符数组

String的不可变性

string s = "Hello";
s += " World";

虽然看起来在修改字符串,但其实

旧字符串仍然存在于内存中,等待GC回收

为什么要设计成不可变

  1. 线程安全(无锁共享)
  2. 哈希值缓存稳定
  3. 可用于字典、集合等键类型
  4. 提升性能(如字符串驻留)
  5. 减少错误(避免被外部修改)

字符串驻留(Interning)

C#编译器会把相同字面值的字符串常量指向同一内存

string a = "abc";
string b = "abc";
Console.WriteLine(object.ReferenceEquals(a, b)); // True

所以字符串字面量是驻留的,避免重复创建

实现机制
  1. 编译器阶段优化 当有多个相同的字符串字面量时,编译器会自动合并为一个驻留字符串

  2. 运行时通过Intern Pool判断 CLR通过内部的字符串池(intern pool)判断字符串是否已存在,并决定是否复用

当在运行时动态创建字符串时(如拼接、读取)时,它不会自动驻留

手动判断/驻留\

  1. 使用string.IsInterned()判断是否驻留
  2. 使用string.Intern()手动驻留 Intern()

驻留池生命周期

string的特殊性

string的分类为引用类型(在堆中分配),但是语义缺执行值语义行为(赋值复制引用,但不可变),比较时按值比较(而不是引用)

string s1 = "abc";
string s2 = s1; // s2指向同一个字符串
s2 = "xyz"; // s2改指向一个新的字符串,s1不受影响
Console.WriteLine(s1) // abc

string的实现

C#中的string是一个类(引用类型),它内部使用char[]表示字符内容

public sealed class String : IComparable, ICloneable, IEnumerable<char>
{
  private readonly int _stringLength;
  private readonly char _firstChar;
  // 实际上还有内部字段存储字符数组
}

由于是sealedString不能被继承

操作符重载

string s = "Hello" + "World"; // 编译器优化为常量”Hello World“

字符串操作效率问题

由于不可变,频繁拼接会创建大量新对象

string result = "";
for (int i = 0; i < 1000; ++i) result += i;

性能极差

推荐使用

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.Append(i);
string result = sb.ToString();

API

方法功能
Length获取字符串长度(字符数)
Substring(start, length)截取子串
IndexOf(char/str)查找字符/子串索引
ToUpper() / ToLower()字母大小写转换
Trim() / TrimStart() / TrimEnd()去除空白字符
Replace(old, new)替换子串
Split(char[])拆分成字符串数组
Contains(string)是否包含子串
StartsWith() / EndsWith()前缀/后缀判断

Dynamic(慎用)

它代表了动态类型系统的入口,让C#拥有了类似Python、JavaScript的运行时绑定的能力

指针类型(Pointer Types)

这是C#中一部分不常用但非常底层的特性,它让C#在特定场景下具备类似C++的裸指针能力

指针类型是C#的unsafe功能,它允许直接访问内存地址、操作指针,就像在C/C++中一样,但需要使用unsafe关键字显式开启

语法

type* pointerName;
unsafe
{
	int x = 10;
	int* ptr = &x;
	Console.WriteLine(*ptr);
}

开启条件
由于C#是类型安全的语言,默认不允许使用指针,因此必须:

  1. 显式使用unsafe修饰代码块或方法
  2. 在项目属性中开启允许不安全代码
unsafe class UnsafeClass
{
  static void Main()
  {
    int x = 123;
	int* p = &x;
	Console.WriteLine(*p); // 123
  }
}

指针可用于所有值类型,不可用于所有引用类型,也就是说,指针蹦年指向托管堆上的对象,这是不安全行为,堆上的对象会被GC移动

C#提供了更安全的替代方式

类型转换

类型转换类别描述
隐式转换(implicit)安全、自动完成,无需强制转换符
显式转换(cast)可能丢失精度,需使用强制转换符 ()
装箱(Boxing)值类型 → 引用类型(object)
拆箱(Unboxing)引用类型 → 值类型(需类型匹配)
使用 Convert安全地处理类型转换,含 null 检查等
Parse / TryParse将字符串转换为数值等基本类型
用户自定义转换自定义 implicitexplicit 运算符

隐式转换(implicit)

隐式转换是指:编译器自动完成类型转换而不显式使用cast操作符的情况

它是语言的类型和操作符重载机制中非常重要的一部分

特点:

内置类型的隐式转换

常见数值类型之间的隐式转换

来源类型目标类型
byteshort, int, long, float, double, decimal
shortint, long, float, double, decimal
intlong, float, double, decimal
floatdouble
charushort, int, uint, long, ulong, float, double, decimal
int i = 100;
double d = i; // 隐式转换:int -> double

float f = 3.14f;
double d2 = f; // 隐式转换:float -> double

自定义类型中的隐式转换

使用implicit关键字来自定义转换操作符

public class Meter
{
  public double value { get; }

  public Meter(double value) => Value = value;

  // 自定义从double到Meter的隐式转换
  public static implicit operator Meter(double value) => return new Meter(value);

  // 自定义从Meter到double的隐式转换
  public static implicit operator double(Meter meter) => return meter.Value;
}

用法

Meter m = 5.0; // 隐式double -> Meter
double d = m; // 隐式Meter -> double

注意事项

  1. 不要滥用隐式转换操作符:过度使用可能会降低代码的可读性,使得类型之间的边界变得模糊
  2. 避免转换歧义:如果两个类型之间同时定义了implicitexplicit,要明确选择
  3. 接口与继承结构中,隐式转换通常用于“上转型”
Stream s = new FileStream(...); // FileStream -> Stream 是隐式转换

示例:封装单位系统

public struct Celsius
{
    public double Degrees { get; }

    public Celsius(double degrees)
    {
        Degrees = degrees;
    }

    public static implicit operator Fahrenheit(Celsius c)
    {
        return new Fahrenheit(c.Degrees * 9 / 5 + 32);
    }
}

public struct Fahrenheit
{
    public double Degrees { get; }

    public Fahrenheit(double degrees)
    {
        Degrees = degrees;
    }

    public static implicit operator Celsius(Fahrenheit f)
    {
        return new Celsius((f.Degrees - 32) * 5 / 9);
    }
}

// 用法
Celsius c = new Fahrenheit(98.6); // 自动转为 Celsius
Fahrenheit f = new Celsius(37);  // 自动转为 Fahrenheit

显式转换(cast)

开发者使用强制类型转换(cast)语法手动进行的类型转换

int i = (int)3.14;

C#中的显式转换用于那些可能丢失信息或失败的转换,因此编译器不会自动完成,必须使用(T)的形式进行转换

内置类型的显式转换

内置数值类型之间存在一些需要显式转换的形式:

来源类型目标类型是否需要显式转换原因
doubleint可能丢失小数部分
longint可能溢出
floatbyte可能精度损失和溢出

示例:

double d = 3.14;
int i = (int)d; // i = 3

long l = 9999999999;
int j = (int)l; // 溢出,j的值将错误

显式转换的本质

C#通过explicit关键字来定义类中的自定义显式类型转换操作符,用来让开发者明确地从一种类型转换为另一种类型

public static explicit operator TargetType(SourceType value)

示例:显式转换一个自定义类为int

public class MyClass
{
  public int Value { get; set; }

  public MyClass(int value) => Value = value;

  public static explicit operator int(MyClass obj)
  {
    return obj.Value;
  }

  public static explicit operator MyClass(int value)
  {
    return new MyClass(value);
  }
}

用法

MyClass a = (MyClass)42; // 显式从int转为MyClass
int x = (int)a; // 显式从MyClass转为int

如果不使用(int)强制转换,上面代码将无法编译

隐式转换 vs 显式转换

对比项隐式转换(implicit)显式转换(explicit)
编译器行为自动进行必须手动 (T)value
安全性必须是无损转换可以是有损或可能失败的转换
用户体验更加自然、易于使用更安全,提醒开发者确认转换逻辑
典型用法场景小类型 → 大类型、单位包装类、语义增强大类型 → 小类型、自定义类结构、需防误用的场景

示例

数值和包装类

public struct Health
{
    public int Value;

    public Health(int value) => Value = value;

    public static explicit operator int(Health h) => h.Value;
    public static explicit operator Health(int v) => new Health(v);
}

// 用法
int hp = (int)new Health(100);
Health h2 = (Health)hp;

从字符串解析对象

public class IPAddress
{
    public string Address { get; private set; }

    private IPAddress(string addr) => Address = addr;

    public static explicit operator IPAddress(string input)
    {
        // 假设格式检查
        if (input.Contains("."))
            return new IPAddress(input);
        throw new FormatException("Invalid IP address");
    }
}

值类型与引用类型之间的转换

装箱(Boxing)=> 值类型 -> 引用类型

将值类型转换为引用类型(通常是object或接口类型),该值会被拷贝一份并放到堆上,返回的是引用

int i = 42;
object obj = i; // 装箱:int -> object

本质:

  1. 在堆上分配一块内存
  2. i的值拷贝到堆上
  3. 包装成一个System.ValueType的实例
  4. 返回该引用(地址)

注意:

拆箱(Unboxing)=> 引用类型 -> 值类型

将一个已经装箱的对象(如object)重新转换为其原始的值类型

object obj = 42; // 装箱
int i = (int)obj; // 拆箱
float f = (float)obj; // 抛出异常,不能拆箱为不同类型

注意:

结构体和接口之间的装箱

struct MyStruct : IMyInterface
{
  public int Value;
  public void DoSomething() => Console.WriteLine(Value);
}

IMyInterface iface = new MyStruct { Value = 10 }; // 装箱
iface.DoSometing(); // iface是一个引用

结构体实现接口时,赋值给接口类型变量会触发装箱,导致拷贝和性能损耗

值类型转引用类型的其他形式

  1. 值类型 ->object/dynamic/接口类型
int a = 5;
object obj = a; // boxing
dynamic d = a; // boxing
IComparable cmp = a; // boxing
  1. 使用泛型装箱陷阱
void Print<T>(T value) => object obj = value; // 如果T是值类型,这里会装箱

值类型转换为引用类型的扩展方式

除了object、接口等默认机制,也可以在自定义类中自定义显式或隐式转换

struct Health
{
  public int value;

  public static implicit operator Health(int value) => new Health { Value = value; };
  public static explicit operator int(Health h) => h.Value;
}

这样就可以

Health h = 100; // 隐式装箱为结构体(无引用)
int value = (int)h;

避免装箱的优化建议

String转基本类型

三种常见的转换方式

方法是否安全失败行为备注
Convert.ToXxx(string)抛出异常快捷但不安全
Xxx.Parse(string)抛出异常比较常见,但需自己 try-catch
Xxx.TryParse(string, out value)返回 false推荐,性能更稳健

Parse

Parse是一组用于将字符串转换为对应基本类型的方法,它们存在于相应类型的静态方法中

int.Parse(string);
float.Parse(string);
DateTime.Parse(string);
Enum.Parse(Type, string)

Parse是强类型转换的一种形式,要求输入格式必须合法,否则会抛出异常

Parse的基本行为
常见类型的转换
int num = int.Parse("123"); // OK
int bad = int.Parse("abc"); // FormatException
float pi = float.Parse("3.14");
bool b1 = bool.Parse("true"); // OK
bool b2 = bool.Parse("FALSE"); // OK
bool b3 = bool.Parse("yes"); // FormatException
DataTime dt = DataTime.Parse("2025-07-29");

支持多种格式,但有区域性依赖(受CultureInfo控制)

enum Direction { Up, Down, Left, Right }

Dirction d = (Direction)Enum.Parse(typeof(Direction), "Up");

// 也可以忽略大小写
Enum.Parse(typeof(Direction), "down", true);  // 返回 Direction.Down

常见异常

异常类型说明
FormatException字符串格式不合法
ArgumentNullException传入的是 null
OverflowException数值超出类型范围(如 int.Parse("999999999999")
自定义类中实现Parse
class Person
{
  public string Name;
  public int Age;

  public static Person Parse(string s)
  {
    var parts = s.Split(',');
    return new Person
    {
      Name = parts[0], 
      Age = int.Parse(parts[1])
    };
  }
}

使用

Person p = Person.Parse("Jeff,24"")

TryParse

TryParse是C#中用于安全地将字符串转换为基本类型的一种方法,它是Parse的安全替代方案。TryParse不会抛出异常,而是返回一个布尔值表示转换是否成功,转换结果通过out参数返回

基本语法
bool TryParse(string s, out T result)
基本类型的TryParse

int.TryParse

string input = "123";
if (int.TryParse(input, out int number))
    Console.WriteLine("转换成功:" + number);
else
    Console.WriteLine("转换失败");

float.TryParse

float.TryParse("3.14", out float pi); // pi = 3.14
float.TryParse("abc", out float pi); // fail = 0

bool.TryParse

bool.TryParse("true", out bool b1); // true
bool.TryParse("yes", out bool b2); // false
Parse vs TryParse
特性ParseTryParse
失败处理方式抛出异常返回 false,不抛异常
推荐程度不推荐(除非能确保输入)推荐用于所有用户输入或不确定值
返回类型转换后的类型bool(成功与否)
性能表现较差(失败时抛异常开销大)优秀,特别是在循环中
Enum.TryParses示例
enum Direction { Up, Down, Left, Right }

Enum.TryParse("Left", out Direction d); // 成功
Enum.TryParse("INVALID", out Dirction d2); // 失败,d2 = default(Direction)
Enum.TryParse("up", ignoreCase: true, out Direction d3); // 忽略大小写

Enum.TryParse<TEnum>(string, bool ignoreCase, out TEnum)是泛型形式,推荐使用

封装通用TryParse方法

如果频繁使用TryParse,或者string要转复杂的大量的自定义类型,可以做以下封装,提高代码扩展性

public static class ParseHelper
{
    public static bool TryParse<T>(string input, out T result)
    {
        result = default;

        var type = typeof(T);

        if (type == typeof(int) && int.TryParse(input, out int i)) { result = (T)(object)i; return true; }
        if (type == typeof(float) && float.TryParse(input, out float f)) { result = (T)(object)f; return true; }
        if (type == typeof(bool) && bool.TryParse(input, out bool b)) { result = (T)(object)b; return true; }
        if (type.IsEnum && Enum.TryParse(type, input, true, out object e)) { result = (T)e; return true; }

        return false;
    }
}

Convert

Convert类是C#中一个非常强大的工具,它提供了一些列静态方法,用于将各种类型之间进行转换

相比于ParseTryParseConvert在类型转换上更为宽松,支持更广泛的类型转换,不仅支持值类型和引用类型之间的转换,还可以处理不同类型之间的转换,如从intdouble,从DateTimestring

Convert类的作用

Convert类位于System命名空间下,提供了一些非常常见的转换方法,它的作用主要包括:

  1. 类型转换: 支持 值类型到值类型、引用类型到值类型、值类型到引用类型 的转换。

  2. 兼容性强: 能够在很多情况下进行类型的宽松转换,例如将数字类型(int、double、float)互相转换,或者将日期类型转换为字符串等。

  3. null 处理: 与 Parse 不同,Convert 不会抛出异常,而是将 null 转换为适当的默认值。

Convert常用方法

Convert类提供了多个静态方法,以下是其中一些常用的方法

object obj = "123";
int num = Convert.ToInt32(obj); // 结果:123

object nullObj = null;
int result = Convert.ToInt32(nullObj); // 结果:0(null 转为默认值)
object obj1 = 123;
string str1 = Convert.ToString(obj1); // 结果:"123"

object obj2 = null;
string str2 = Convert.ToString(obj2); // 结果""(空字符串)
object obj = "2025-07-29";
DateTime date = Convert.ToDateTime(obj); // 结果:2025/07/-29

object obj2 = null;
DateTime date2 = Convert.ToDateTime(obj2); // 结果:01/01/0001 00:00:00(默认值)
object obj = 3.14f;
decimal dec = Convert.ToDecimal(obj);  // 结果:3.14
object obj = 123;
int num = (int)Convert.ChangeType(obj, typeof(int));  // 结果:123

object strObj = "2025-07-29";
DateTime date = (DateTime)Convert.ChangeType(strObj, typeof(DateTime));  // 结果:2025/07/29

Convert.ChangeType是通过反射来实现的,适用于需要动态类型转换的情况 Convert.ChangeType没有对null的特殊处理,所以当将null转换为非空类型时会抛出异常,需要进行显式处理

Convert内部原理

  1. 基于 IConvertible 接口:C# 中的大部分类型(如 int、float、double、DateTime、string 等)都实现了 IConvertible 接口,Convert 类使用该接口提供的转换方法来执行类型转换。
  2. 处理 null 值:Convert 类会自动将 null 转换为该类型的默认值。例如,Convert.ToInt32(null) 会返回 0,Convert.ToDateTime(null) 会返回 DateTime.MinValue,而Convert.ChangeType需要显式处理
  3. 类型溢出处理:Convert 会根据目标类型的大小范围执行适当的类型溢出检查。如果值超出目标类型的范围,Convert 会抛出 OverflowException。
  4. 宽松转换:Convert 会尽可能地进行宽松的类型转换,比如将 int 转换为 double,将 float 转换为 decimal 等。但有些类型无法自动转换,如将 string 转换为 int(必须是格式正确的字符串)。