C#数据类型

C#数据类型

特性 值类型 引用类型
存储位置 栈 (通常)
赋值行为 复制值 复制引用
默认值 0、false等 null
继承 不能被继承 (sealed) 可继承 (除非sealed)
null 不可为null (除非Nullable) 可为null
性能 较快 (无GC压力) 较慢 (GC管理)
装箱 会发生 不会

值类型

值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的

复制代码
System.Object
├── 值类型 (System.ValueType)
│   ├── System.Enum (所有枚举的基类)
│   │   └── 自定义枚举
│   ├── 简单值类型(struct)
│   │   ├── System.Boolean
│   │   ├── System.Char
│   │   ├── System.Byte, SByte, Int16, UInt16, Int32, UInt32, UInt128, Int64, UInt64, Int128
│   │   ├── System.Single, Double, Decimal
│   │   └── System.IntPtr, UIntPtr
│   ├── System.Nullable<T>
│   └── 自定义结构体

可空类型

值类型不能为 null可空类型 允许值类型(如 int, bool, DateTime 等)拥有 null 值,可空类型解决了这一限制,常用于数据库字段映射、API 响应处理等场景

1、写法T?与Nullable<T>

c# 复制代码
// 写法一:使用 ? 后缀(推荐,更简洁)
int? a = 10;
bool? b = null;
DateTime? c = null;

// 写法二:使用 Nullable<T> 泛型结构
Nullable<int> d = null;

2、HasValue与Value

c# 复制代码
int? a = 10;
bool? b = null;

if (a.HasValue)
{
    Console.WriteLine(a.Value);
}
Console.WriteLine(b.HasValue ? b.Value : false);

3、 GetValueOrDefault

避免异常的安全取值方式,如果为 null 则返回默认值(如 int 的默认值是 0)

c# 复制代码
int? a = 10;
bool? b = null;

Console.WriteLine(a.GetValueOrDefault());
Console.WriteLine(a.GetValueOrDefault(-1));
Console.WriteLine(b.GetValueOrDefault());
Console.WriteLine(b.GetValueOrDefault(false));

4、运算符??

如果左操作数为 null,则返回右操作数的值

c# 复制代码
int? a = 10;
bool? b = null;

Console.WriteLine(a ?? -1);
Console.WriteLine(b ?? false);

5、空条件运算符:?.?[]

主要用于引用类型、嵌套可空类型

c# 复制代码
int? a = 10;
bool? b = null;

Console.WriteLine(a?.ToString() ?? "无");
Console.WriteLine(d?.ToString() ?? "无");

// 引用类型
string text = null;
Console.WriteLine(text?.Length ?? 0);

6、强制转换

可以使用显式转换,但如果值为 null 会抛出异常

c# 复制代码
int? a = 10; 
int b = (int)a; 

int? x = null; 
int y = (int)x; // 抛出 InvalidOperationException

引用类型

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用(即内存地址/指针),实际的数据存储在堆(Heap)上

  • 引用类型存储对象引用,对象本身在堆上

  • 默认值为 null,需要注意空引用异常

  • 赋值时复制引用,多个变量可能指向同一对象

  • 由垃圾回收器自动管理内存

  • C# 8.0+ 的可空引用类型帮助避免空引用问题

    System.Object
    ├── 引用类型 (直接继承 Object)
    ├── System.String
    ├── System.Array (所有数组的基类)
    ├── System.Delegate (所有委托的基类)
    │ └── System.MulticastDelegate
    │ └── 自定义委托
    ├── System.Exception
    ├── System.Type
    └── 自定义类

c# 复制代码
static void Main(string[] args)
{
    List<int> list1 = new List<int> { 1, 2, 3 };
    var list2 = list1;
    var list3 = list1;

    list3[2] = 30;
    list2[1] = 20;
    list1[0] = 10;

    foreach (var item in list1)
    {
        Console.Write(item + ", ");
    }

    Console.WriteLine();
    foreach (var item in list2)
    {
        Console.Write(item + ", ");
    }
    
    Console.WriteLine();
    foreach (var item in list3)
    {
        Console.Write(item + ", ");
    }

    Console.ReadKey();
}

可空引用类型

可空引用类型是 C# 8.0 引入的特性,旨在帮助开发者在编译时避免 NullReferenceException(空引用异常)

c# 复制代码
// C# 8.0 之前 
string name = null; // 编译通过 
Console.WriteLine(name.Length); // 运行时抛出 NullReferenceException

// C# 8.0 之后 
string? name1 = null; // 明确声明可空 
string name2 = null; // 编译器警告
选项 说明
enable 启用可空引用类型,所有警告生效
disable 禁用(默认,兼容旧代码)
warnings 仅显示警告,不改变语义
annotations 仅启用注解,不显示警告

1、全局配置

xml 复制代码
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Nullable>enable</Nullable>  <!-- 全局启用 -->
  </PropertyGroup>
</Project>

2、按文件配置

c# 复制代码
#nullable enable   // 启用可空引用类型检查
// 代码...
#nullable disable  // 禁用

#nullable restore  // 恢复项目设置

3、Null 条件运算符 ?.

c# 复制代码
string? name = GetName();
int? length = name?.Length;  // 如果 name 为 null,返回 null

// 链式调用
string? city = person?.Address?.City;
string cityName = person?.Address?.City ?? string.Empty;

4、Null 合并运算符 ??

c# 复制代码
string? name = null;
string displayName = name ?? "未知";  // 输出:未知

// C# 8.0 空合并赋值
name ??= "默认名称";  // 仅当 name 为 null 时赋值

5、Null 包容运算符 !(Null-forgiving)

c# 复制代码
string? nullable = null;
string nonNull = nullable!;  // 告诉编译器:"我确定它不为 null"

// ⚠️ 危险:如果实际为 null,运行时仍会抛异常
Console.WriteLine(nonNull is null);

元组类型

在 C# 7.0 之前使用,基于 System.Tuple 类,属于引用类型;之后基于 System.ValueTuple 结构体,属于值类型。在新版本中新老特性都保留了,使用的时候稍微注意一下

元组有个数限制,数量非常多的可以考虑其他方式

特性 System.ValueTuple (C# 7.0+) System.Tuple (旧版)
类型 结构体(值类型) 类(引用类型)
性能 更好(无堆分配) 较差(堆分配)
语法 (int, string) Tuple<int, string>
元素访问 Item1, Item2 或自定义名称 只能 Item1, Item2
可变性 字段是可变的 字段是只读的
比较 支持 ==!= 需要手动实现
c# 复制代码
static void Main(string[] args)
{
    // 老版用法
    var tuple1 = Tuple.Create(1, "Hello");
    Console.WriteLine(tuple1.Item1);
    Console.WriteLine(tuple1.Item2);

    tuple1 = new Tuple<int, string>(2, "张三");
    Console.WriteLine(tuple1.Item1);
    Console.WriteLine(tuple1.Item2);


    // 新版用法
    var tuple2 = (10, "World");
    Console.WriteLine(tuple2.Item1);
    Console.WriteLine(tuple2.Item2);

    tuple2 = new ValueTuple<int, string>(20, "李四");
    Console.WriteLine(tuple2.Item1);
    Console.WriteLine(tuple2.Item2);
    
    // 字段可变
	tuple2.Item2 = "西门吹雪";
	Console.WriteLine(tuple2.Item2);
	
	// 创建时指定名称
	var namedTuple = (Id: 1, Name: "叶孤城");
	Console.WriteLine(namedTuple.Name);
	
	// 声明时指定名称
	(int age, string name) person = (30, "李四");
	Console.WriteLine(person.age);
	Console.WriteLine(person.name);

        // 方法返回值是元组
    var result = Calc(new[] { 1, 2, 3, 4, 5 });
    Console.WriteLine($"max: {result.max}, min: {result.min}, length:{result.length}");

    Console.ReadKey();
}

public static (int max, int min, int length) Calc(int[] numbers)
{
    int max = numbers.Max();
    int min = numbers.Min();
    return (max, min, numbers.Length);
}

元组解构赋值

  • 全部解构
c# 复制代码
static void Main(string[] args)
{
	var tuple1 = (1, "张三", 20);
	var (x, y, z) = tuple1;

	Console.WriteLine(x);
	Console.WriteLine(y);

	Console.ReadKey();
}
  • 部分解构

弃元 _ 不会分配内存,它只是告诉编译器"这里有个值,但我不需要"

c# 复制代码
 static void Main(string[] args)
 {
	 var tuple1 = (1, "张三", 20);
	 // 只关心第一个和第三个元素
	 var (id, _, age) = tuple1;
	 Console.WriteLine($"ID: {id}, age: {age}");

	 Console.ReadKey();
 }

按名称解构,不用管顺序

c# 复制代码
 static void Main(string[] args)
 {
	 var tuple1 = (Id: 1, Name: "叶孤城", Age: 20);
	 // 只关心第一个和第三个元素
	 var (Id, Age, _) = tuple1;
	 Console.WriteLine($"ID: {Id}, Age: {Age}");

	 Console.ReadKey();
 }

元组比较

  • 元素数量必须相同
  • 元素类型必须一一对应相同
  • 元组的每个对应元素都必须相等
c# 复制代码
static void Main(string[] args)
{
	var tuple1 = (1, "hello");
	var tuple2 = (1, "hello");
	var tuple3 = (2, "world");
	var tuple4 = ("1", "hello");

	Console.WriteLine(tuple1 == tuple2);
	Console.WriteLine(tuple1 == tuple3);
	Console.WriteLine(tuple1 != tuple3);
	// 编译报错(类型不匹配)
	// Console.WriteLine(tuple1 != tuple4);

	Console.ReadKey();
}

指针类型

Dynamic类型