C# 数据类型

C# 类型系统是语言设计上的精品——既严格(编译期类型检查)又灵活(支持泛型、自动转换)。这一篇我们把 C# 的核心类型一次过完,包括值类型与引用类型的根本区别、为什么财务计算要用 decimal、enum 怎么用。

一、两大分类:值类型 vs 引用类型

C# 所有类型分两大阵营:

这个区别是 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

选择建议:

为什么财务要 decimal?因为 double 用二进制存小数,0.1 实际是 0.1000000000000000055...,累加会出误差;decimal 用十进制存,0.1m + 0.2m 严格等于 0.3m。

三、布尔类型 bool

bool 只有两个值:truefalse

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("不是数字");

几个注意点:

八、装箱与拆箱(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;   // 25

C# 8+ 还引入了可为空引用类型(NRT),让引用类型(如 string)也能用 ? 标注是否可空。新项目默认开启,大幅减少 NullReferenceException。

十、类型系统速查表

小结

这一篇覆盖了 C# 的所有核心类型。记住核心二分法:值类型复制、引用类型共享;记住一个反直觉:财务用 decimal;记住一个工程实践:用泛型集合避免装箱。下一篇我们看 C# 的运算符——很基础但有几个小特性值得说。

← 上一篇 C# 变量

下一篇 C# 运算符

✈️💬