C#基础(一):数据类型
C# 是强类型语言,所有类型均派生自 System.Object,整体分为值类型、引用类型两大分支,底层内存分配、赋值行为、生命周期完全不同。下面从底层原理、细分类型、代码实践、常见误区四个维度展开讲解。
一、值类型(Value Types)
1.1 核心本质
值类型的变量直接存储数据本身,通常分配在线程栈(Stack)上,随方法执行结束自动释放,无需 GC 回收。
- 赋值时会创建完整的数据副本,两个变量互不影响
- 隐式继承自
System.ValueType(该类又继承自object) - 不能被继承,默认是隐式密封的
- 默认值为其 “零值”(如
int默认 0,bool默认 false)
1.2 简单值类型
1.2.1 整型
| 类型 | 占用字节 | 取值范围 | 说明 |
|---|---|---|---|
sbyte |
1 | -128 ~ 127 | 有符号字节 |
byte |
1 | 0 ~ 255 | 无符号字节,常用于二进制数据 |
short |
2 | -32768 ~ 32767 | 短整型 |
ushort |
2 | 0 ~ 65535 | 无符号短整型 |
int |
4 | -2^31 ~ 2^31-1 | 最常用整型,默认整数字面量为 int |
uint |
4 | 0 ~ 2^32-1 | 无符号整型 |
long |
8 | -2^63 ~ 2^63-1 | 长整型,大数值场景 |
ulong |
8 | 0 ~ 2^64-1 | 无符号长整型 |
// 整型字面量后缀 int a = 100; // 默认int long b = 100L; // L后缀指定long uint c = 100U; // U后缀指定uint ulong d = 100UL; // UL后缀指定ulong // 二进制与十六进制字面量(C# 7.0+) int binary = 0b1010; // 二进制,值为10 int hex = 0xFF; // 十六进制,值为255
1.2.2 浮点型
| 类型 | 占用字节 | 精度 | 取值范围 | 适用场景 |
|---|---|---|---|---|
float |
4 | 约 7 位十进制 | ±1.5e-45 ~ ±3.4e38 | 精度要求不高、数据量大的场景 |
double |
8 | 约 15-16 位十进制 | ±5e-324 ~ ±1.7e308 | 科学计算、默认浮点类型 |
decimal |
16 | 28-29 位十进制 | ±1e-28 ~ ±7.9e28 | 金融、货币计算(精度优先) |
关键区别:float/double 是二进制浮点,存在精度丢失问题;decimal 是十进制浮点,专门为财务场景设计,运算速度略慢。
float f = 3.14f; // 必须加f后缀 double d = 3.14; // 默认浮点字面量为double decimal money = 99.99m; // 必须加m后缀,金融计算首选 // 经典精度丢失问题 double x = 0.1 + 0.2; Console.WriteLine(x); // 输出 0.30000000000000004,二进制浮点固有缺陷 decimal y = 0.1m + 0.2m; Console.WriteLine(y); // 输出 0.3,精度准确
1.2.3 字符与布尔
char:2 字节,Unicode UTF-16 字符,支持转义字符、Unicode 转义bool:1 字节,只有true/false两个值,不能与整型互转
char c1 = 'A'; char c2 = '\u0041'; // Unicode转义,等同于'A' char tab = '\t'; // 转义字符:制表符 bool isSuccess = true; // 错误写法:if (isSuccess == 1) { } C#不支持bool与数字隐式转换
1.3 结构体(struct)
结构体是用户自定义值类型,用于封装小型相关变量组,适合轻量数据模型。
核心特点
- 栈上分配(除非被装箱),性能优于类
- 不支持继承,不能显式指定无参构造函数(C# 10+ 允许自定义无参构造)
- 必须在构造函数中为所有字段赋值
// 基础结构体定义 public struct Point { // 字段 public int X; public int Y; // 有参构造函数 public Point(int x, int y) { X = x; Y = y; } // 方法 public double DistanceToOrigin() { return Math.Sqrt(X * X + Y * Y); } } // 使用 Point p1 = new Point(3, 4); Point p2 = p1; // 值拷贝,p1和p2是两个独立对象 p2.X = 10; Console.WriteLine(p1.X); // 输出3,不受p2修改影响
高级结构体特性
只读结构体(C# 7.2+):所有成员不可变,编译器优化避免不必要
public readonly struct ReadonlyPoint { public int X { get; } public int Y { get; } public ReadonlyPoint(int x, int y) { X = x; Y = y; } }
ref 结构体(C# 7.2+):只能分配在栈上,不能被装箱,用于高性能内存操作(如 Span<T> 就是 ref struct)
public ref struct BufferWrapper { public Span<byte> Buffer; // 不能作为泛型参数、不能被装箱、不能在类中声明 }
1.4 枚举(enum)
枚举是命名的整型常量集合,增强代码可读性,底层默认是 int 类型。
基础用法
// 默认底层类型为int,从0开始递增 public enum OrderStatus { Pending, // 0 Processing, // 1 Shipped, // 2 Completed, // 3 Cancelled // 4 } // 指定底层类型与初始值 public enum WeekDay : byte { Monday = 1, Tuesday = 2, Wednesday = 3 } // 使用 OrderStatus status = OrderStatus.Processing; Console.WriteLine(status); // 输出 Processing(字符串形式) Console.WriteLine((int)status); // 输出 1(强转获取数值)
Flags 位枚举(高频实用)
标注 [Flags] 特性后,枚举可通过按位运算组合多个值,适合表示多选状态。
[Flags] public enum Permission { None = 0, // 0000 Read = 1 << 0, // 0001 = 1 Write = 1 << 1, // 0010 = 2 Delete = 1 << 2, // 0100 = 4 Admin = Read | Write | Delete // 0111 = 7 } // 位运算操作 Permission userPerm = Permission.Read | Permission.Write; // 组合权限 // 判断是否包含某权限 bool canWrite = userPerm.HasFlag(Permission.Write); // true // 移除权限 userPerm = userPerm & ~Permission.Write; // 输出结果 Console.WriteLine(userPerm); // 输出 Read
二、引用类型(Reference Types)
2.1 核心本质
引用类型的变量存储的是数据的内存地址(引用),实际数据存储在托管堆(Heap)上,由 GC(垃圾回收器)负责生命周期管理。
- 赋值时只复制引用地址,多个变量可指向同一个堆对象
- 默认值为
null(不指向任何对象) - 支持继承、多态
2.2 核心引用类型分类
| 类型 | 说明 |
|---|---|
class |
自定义引用类型,面向对象核心载体 |
interface |
行为契约,支持多继承 |
delegate |
类型安全的函数指针,用于回调与事件 |
record / record class |
C# 9.0+,不可变引用类型,默认按值比较 |
record struct |
C# 10+,不可变值类型 record |
2.3 特殊引用类型详解
2.3.1 string(字符串)
string 是 System.String 的别名,是最特殊的引用类型:
- 不可变性:字符串一旦创建就无法修改,任何修改操作都会生成新字符串
- 字符串驻留池:相同字面量字符串在内存中只存一份,复用引用
- 支持
==按值比较(编译器重写了运算符)
// 不可变性演示 string s1 = "hello"; string s2 = s1; s1 = "world"; // 修改s1,实际是创建了新字符串,s2不受影响 Console.WriteLine(s2); // 输出 hello // 字符串驻留池 string a = "abc"; string b = "abc"; // 虽然是引用类型,但指向堆中同一个对象 Console.WriteLine(object.ReferenceEquals(a, b)); // 输出 true // 动态拼接不进入驻留池 string c = new string(new char[] { 'a', 'b', 'c' }); Console.WriteLine(object.ReferenceEquals(a, c)); // 输出 false // 大量拼接用StringBuilder,避免生成大量临时字符串 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.Append(i); } string result = sb.ToString();
2.3.2 object(万物基类)
object 是所有类型的基类,任何类型都可以隐式转换为 object。
- 装箱的核心载体
- 提供四个基础方法:
Equals()、GetHashCode()、GetType()、ToString()
2.3.3 dynamic(动态类型)
C# 4.0 引入,绕过编译时类型检查,在运行时解析类型。
- 适合与 COM、动态语言交互,或反射简化
- 失去智能提示,性能略差,异常延迟到运行时
dynamic dyn = 100; Console.WriteLine(dyn.GetType()); // System.Int32 dyn = "hello"; // 运行时改变类型 Console.WriteLine(dyn.Length); // 编译通过,运行时检查是否有Length属性 // 对比var:var是编译时类型推断,本质还是强类型 var v = 100; // v编译时就是int // v = "hello"; // 编译报错
三、值类型 vs 引用类型 核心差异
3.1 维度对比表
| 对比维度 | 值类型 | 引用类型 |
|---|---|---|
| 内存分配 | 主要在栈 | 托管堆 |
| 变量存储 | 数据本身 | 数据的内存地址 |
| 赋值行为 | 创建完整副本 | 只复制引用 |
| 默认值 | 零值(0/false 等) | null |
| 继承性 | 不能被继承,隐式密封 | 支持继承 |
| GC 影响 | 不触发 GC,随栈自动释放 | 由 GC 回收 |
| 基类 | System.ValueType | System.Object |
3.2 赋值行为差异演示
// 值类型:复制数据,互不影响 int a = 10; int b = a; b = 20; Console.WriteLine($"a={a}, b={b}"); // a=10, b=20 // 引用类型:复制引用,指向同一对象 public class Person { public int Age; } Person p1 = new Person { Age = 10 }; Person p2 = p1; p2.Age = 20; Console.WriteLine($"p1.Age={p1.Age}, p2.Age={p2.Age}"); // 均为20
3.3 参数传递的补充说明
默认情况下,无论值类型还是引用类型,参数都是值传递:
- 值类型:传递数据副本
- 引用类型:传递引用地址的副本(指向同一个堆对象)
加 ref/out 后变为引用传递:
- 值类型:传递变量地址,方法内修改会影响原变量
- 引用类型:传递引用的引用,可修改原变量指向的对象
// 值类型按引用传递 void ChangeValue(ref int num) { num = 999; } int x = 10; ChangeValue(ref x); Console.WriteLine(x); // 999,原变量被修改
四、可空类型
4.1 可空值类型(Nullable<T>)
值类型默认不能为 null,Nullable<T> 结构体为值类型增加了 “空值” 能力,语法糖为 T?。
// 两种写法等价 int? num1 = null; Nullable<int> num2 = null; // 核心属性与方法 int? value = 10; Console.WriteLine(value.HasValue); // true,是否有值 Console.WriteLine(value.Value); // 10,获取值(无值时抛异常) Console.WriteLine(value.GetValueOrDefault()); // 安全获取,无值返回默认值 // 配合空合并运算符 ?? int result = num1 ?? 0; // num1为null则取0 // 空条件运算符 int? length = str?.Length; // str为null时直接返回null,不抛空引用异常
4.2 可空引用类型(C# 8.0+)
为解决引用类型空引用异常问题,C# 8.0 引入可空引用类型,属于编译时检查。
string:不可空引用,编译器保证不会为 nullstring?:可空引用,明确表示可能为 null,使用前需判空
#nullable enable // 开启可空上下文 string notNullStr = "hello"; // 不可空 // notNullStr = null; // 编译器警告 string? maybeNullStr = null; // 可空 // 使用前需判空,否则编译器警告 if (maybeNullStr != null) { Console.WriteLine(maybeNullStr.Length); }
注意:可空引用类型只是编译时检查,运行时和普通引用类型无区别,不能完全杜绝空引用异常。
五、装箱与拆箱(Boxing & Unboxing)
5.1 核心原理
- 装箱:将值类型转换为
object或接口类型,会在堆上分配对象并复制值 - 拆箱:将装箱后的引用类型转换回值类型,从堆中复制值到栈
这是 C# 类型系统的核心特性,但频繁装箱拆箱会带来性能损耗和 GC 压力。
5.2 代码演示
int num = 123; // 装箱:int -> object object obj = num; // 堆上创建副本,产生内存分配 // 拆箱:object -> int int num2 = (int)obj; // 错误拆箱:类型不匹配会抛InvalidCastException // long l = (long)obj; // 运行时异常,装箱时是int,只能拆回int
5.3 常见发生场景
- 值类型赋值给
object/ 接口变量 - 调用非泛型集合(如
ArrayList)添加值类型元素 - 值类型调用
ToString()、Equals()等基类方法(部分情况会优化)
// 非泛型ArrayList:每个元素都会装箱,性能差 ArrayList list = new ArrayList(); list.Add(1); // 装箱 list.Add(2); // 装箱 // 泛型List<int>:无装箱,性能更好(推荐) List<int> genericList = new List<int>(); genericList.Add(1); // 无装箱
5.4 最佳实践
- 优先使用泛型集合,避免非泛型集合
- 避免频繁在值类型和 object 之间转换
- 高性能场景使用
Span<T>、Memory<T>减少装箱
六、类型转换全解
6.1 隐式转换
编译器自动执行,无数据丢失,比如小范围整型转大范围整型、子类转父类。
int i = 100; long l = i; // int隐式转long,无精度丢失 string s = "abc"; object obj = s; // 子类隐式转父类
6.2 显式转换(强制转换)
需要手动写 (T)value,可能丢失精度或抛异常。
long l = 100; int i = (int)l; // 强制转换 // 数值溢出检查 checked { int max = int.MaxValue; // int overflow = max + 1; // 抛出OverflowException }
6.3 as 与 is 运算符
as:转换失败返回 null,不抛异常,只能用于引用类型和可空值类型is:判断类型是否匹配,返回 bool,C# 7.0 + 支持模式匹配
object obj = "hello"; // as转换 string? str = obj as string; if (str != null) { /* 使用str */ } // is模式匹配(推荐写法) if (obj is string s) { Console.WriteLine(s.Length); // 直接使用s,无需二次转换 } // 逻辑取反模式(C# 9.0+) if (obj is not null) { } // 属性模式 if (obj is Person { Age: > 18 } p) { }
6.4 Convert 类
通用类型转换,支持字符串转值类型,内部做了容错处理。
string numStr = "123"; int num = Convert.ToInt32(numStr); double d = Convert.ToDouble("3.14");
6.5 自定义类型转换
在类型中定义 implicit(隐式)或 explicit(显式)转换运算符。
public struct Celsius { public double Value; public Celsius(double value) => Value = value; // 隐式转换:Celsius -> double public static implicit operator double(Celsius c) => c.Value; // 显式转换:double -> Celsius public static explicit operator Celsius(double d) => new Celsius(d); } // 使用 Celsius c = new Celsius(25.5); double temp = c; // 隐式转换 Celsius c2 = (Celsius)30.0; // 显式转换
七、常见坑点与最佳实践
- 结构体不要设计过大:超过 16 字节建议用类,否则赋值拷贝开销大
- 不要用
==比较 object 装箱的值类型:会比较引用而非值 - 字符串拼接大量数据必须用
StringBuilder:避免生成大量临时字符串增加 GC 压力 - 可空引用类型只是编译检查:不要依赖它保证运行时安全,关键逻辑仍需判空
- 枚举默认从 0 开始:始终为 0 值设置合理的默认成员,避免未初始化枚举出现意外值
- decimal 不支持无符号整型直接计算:跨类型运算时注意类型兼容

浙公网安备 33010602011771号