C# 数据类型
C# 类型系统是语言设计上的精品——既严格(编译期类型检查)又灵活(支持泛型、自动转换)。这一篇我们把 C# 的核心类型一次过完,包括值类型与引用类型的根本区别、为什么财务计算要用 decimal、enum 怎么用。
一、两大分类:值类型 vs 引用类型
C# 所有类型分两大阵营:
- 值类型(Value Type):变量直接存数据本身。赋值时复制。包括
int、double、bool、char、struct、enum。 - 引用类型(Reference Type):变量存的是数据的引用(地址)。赋值时只复制引用,两个变量指向同一对象。包括
string、object、所有class、数组、接口、委托。
这个区别是 C# 入门最重要的概念。看个直观例子:
// 值类型:复制
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a); // 10(没被改)
Console.WriteLine(b); // 20
// 引用类型:共享
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;
arr2[0] = 99;
Console.WriteLine(arr1[0]); // 99(被改了!)
Console.WriteLine(arr2[0]); // 99记忆点:struct 和 enum 是值类型,class 是引用类型。string 虽然是引用类型,但表现像值类型(不可变 + 重写了相等比较),日常当值类型用。
二、整数与浮点类型
C# 提供丰富的数值类型,按需选择:
// 整数类型(按范围/位宽选)
byte b = 255; // 8 位无符号
short s = -1000; // 16 位有符号
int i = 100000; // 32 位有符号(最常用)
long l = 9000000000L; // 64 位有符号(末尾加 L)
// 浮点类型
float f = 3.14f; // 32 位(末尾加 f,精度 7 位)
double d = 3.1415926535; // 64 位(精度 15-16 位,默认)
decimal m = 19.99m; // 128 位(末尾加 m,精度 28-29 位,财务首选)
// 财务计算必须用 decimal
decimal price = 19.99m;
decimal tax = price * 0.06m;
Console.WriteLine(price + tax); // 21.1894选择建议:
- 整数默认用
int(32 位足够大部分场景);大数用long;字节流用byte。 - 小数默认用
double(科学计算、图形、游戏);财务计算必须用decimal(避免 0.1+0.2 的精度坑)。 float较少用,主要在性能敏感的图形/游戏场景。
为什么财务要 decimal?因为 double 用二进制存小数,0.1 实际是 0.1000000000000000055...,累加会出误差;decimal 用十进制存,0.1m + 0.2m 严格等于 0.3m。
三、布尔类型 bool
bool 只有两个值:true 或 false。
bool isReady = true;
bool hasError = false;
// 关系与逻辑运算的结果都是 bool
bool adult = 20 > 18;
bool canDrive = adult && !hasError;
// C# 的 bool 不能当 int 用(和 C/C++ 不同)
// int x = true; // ❌ 编译报错
// if (1) { } // ❌ 必须是 bool 表达式
if (true) { } // ✅注意 C# 比 C/C++ 严格:bool 不能和整数互转,if 条件必须是 bool 表达式。这能避免一大批经典 bug(如 if (x = 0) 误写)。
四、字符 char 与字符串 string
// char:单个 Unicode 字符,单引号包裹
char c1 = 'A';
char c2 = '中'; // 中文也行
char c3 = '\n'; // 换行符
// string 是字符串(引用类型,双引号)
string s1 = "Hello";
string s2 = "你好";
// char 和 string 不是一回事
char c = 'A';
string s = "A"; // 长度 1 的字符串char 是单个 Unicode 字符(16 位),单引号;string 是字符序列(引用类型,不可变),双引号。两者不能混用,但可以互转:
char c = 'A';
string s = c.ToString(); // "A"
char first = "Hello"[0]; // 'H'
char[] arr = "Hello".ToCharArray();五、枚举 enum
枚举用来定义一组命名常量,代码可读性大大提升:
// 定义枚举
public enum Color
{
Red, // 0
Green, // 1
Blue, // 2
Yellow = 10 // 显式赋值
}
// 使用
Color c = Color.Green;
Console.WriteLine(c); // Green
Console.WriteLine((int)c); // 1
// Flags 枚举(位运算组合)
[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Execute = 4
}
Permission p = Permission.Read | Permission.Write;
Console.WriteLine(p); // Read, Write枚举底层是 int(默认),每个值对应一个数字。用 [Flags] 标记可以做位运算组合,这是权限系统的经典实现:Permission.Read | Permission.Write 表示"可读可写"。
六、struct 结构体
struct 是自定义值类型,语法和 class 几乎一样,但赋值时复制:
public struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
Point p1 = new Point(1, 2);
Point p2 = p1; // 复制(因为是 struct)
p2.X = 99;
Console.WriteLine(p1.X); // 1(没被改)struct 适合小而轻的数据(如 Point、Color、DateTime),避免堆分配开销。日常开发用 class 居多,struct 是特殊场景优化。
七、类型转换
类型转换分隐式和显式两种:
// 隐式转换(安全,无精度损失)
int i = 100;
long l = i; // int → long,自动
double d = i; // int → double,自动
// 显式转换(可能丢精度,需手动)
double pi = 3.14;
int n = (int)pi; // 3,小数部分被截断
long big = 9999999999L;
int small = (int)big; // 溢出,结果不可预期
// Convert 类:在兼容类型间转换,带异常检查
string s = "42";
int n1 = Convert.ToInt32(s);
double d1 = Convert.ToDouble("3.14");
// Parse:字符串 -> 数值(失败抛异常)
int n2 = int.Parse("100");
// TryParse:不抛异常,推荐
if (int.TryParse("abc", out int n3))
Console.WriteLine("成功");
else
Console.WriteLine("不是数字");几个注意点:
- 隐式转换:从"小类型"到"大类型"(int → long → double),无精度损失,自动。
- 显式转换:从"大类型"到"小类型",可能丢精度或溢出,需手写
(类型)。 - 字符串转数字:推荐用
TryParse,不会抛异常,适合用户输入场景。
八、装箱与拆箱(Boxing / Unboxing)
把值类型转成 object(或父类)叫装箱,反过来叫拆箱,有性能开销,应尽量避免:
int n = 42;
object box = n; // 装箱:在堆上分配,box 引用它
int m = (int)box; // 拆箱:取出值类型
// ❌ 避免这样写(每次都装箱)
ArrayList list = new ArrayList();
list.Add(1); // 装箱
list.Add(2); // 装箱
// ✅ 用泛型集合(无装箱)
List<int> nums = new List<int>();
nums.Add(1); // 直接存,无装箱这就是为什么现代 C# 强推泛型集合(List、Dictionary),而不是老的非泛型 ArrayList。
九、可空类型(Nullable)
值类型默认不能为 null。但有时候"没有值"很重要(比如数据库字段、API 响应),这时用可空类型 T?:
// 值类型默认不能为 null
// int x = null; // ❌
// Nullable<T> 或 T? :允许 null
int? maybeAge = null;
int? realAge = 25;
// 检查是否为 null
if (maybeAge.HasValue)
Console.WriteLine(maybeAge.Value);
else
Console.WriteLine("没有值");
// ?? 运算符:null 时给默认值
int age = maybeAge ?? 0; // 0
int age2 = realAge ?? 0; // 25C# 8+ 还引入了可为空引用类型(NRT),让引用类型(如 string)也能用 ? 标注是否可空。新项目默认开启,大幅减少 NullReferenceException。
十、类型系统速查表
- 整数:byte、short、int、long(有/无符号版本如 sbyte、ushort、uint、ulong)
- 浮点:float(32)、double(64,默认)、decimal(128,财务)
- 布尔:bool(true / false)
- 字符:char(Unicode 16 位)
- 字符串:string(不可变引用类型)
- 对象:object(所有类型的基类)
- 动态:dynamic(运行时类型)
- 自定义:class、struct、enum、interface、record
小结
这一篇覆盖了 C# 的所有核心类型。记住核心二分法:值类型复制、引用类型共享;记住一个反直觉:财务用 decimal;记住一个工程实践:用泛型集合避免装箱。下一篇我们看 C# 的运算符——很基础但有几个小特性值得说。
← 上一篇 C# 变量
下一篇 C# 运算符 →