C#数据类型
- 值类型
- 可空类型
-
- 1、写法`T?`与Nullable`<T>`
-
- 2、HasValue与Value
- [3、 `GetValueOrDefault`](#3、
GetValueOrDefault) - 4、运算符`??`
- [5、空条件运算符:`?.` 和 `?\[\]`](#5、空条件运算符:
?.和?[]) - 6、强制转换
- 引用类型
- 元组类型
- 指针类型
- Dynamic类型
| 特性 | 值类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈 (通常) | 堆 |
| 赋值行为 | 复制值 | 复制引用 |
| 默认值 | 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();
}
