C# 中值类型的实现示例
值类型和引用类型是 C# 类型系统的两大支柱。值类型变量"直接装数据",引用类型变量"装数据地址"——这看似简单的差别,却深刻影响着赋值、传参、方法返回的行为。这篇来彻底拆解。
- 核心行为:值类型复制的是数据实例本身
- 重要警告:值类型包含引用类型成员时的"浅复制"陷阱
- 值类型的三种种类:结构、枚举、联合声明
- 简单类型的特殊性:文字、常量、编译期求值
- struct 约束与 System.Enum 约束
一、核心行为:赋值即复制
值类型的变量直接包含类型实例,赋值时复制整个实例。
public struct MutablePoint
{
public int X;
public int Y;
public MutablePoint(int x, int y) => (X, Y) = (x, y);
public override string ToString() => $"({X}, {Y})";
}
var p1 = new MutablePoint(1, 2);
var p2 = p1; // 复制整个结构体
p2.Y = 200; // 只影响 p2
Console.WriteLine(p1); // (1, 2) ✅ p1 不受影响
Console.WriteLine(p2); // (1, 200)
方法传参也是复制:
static void MutateAndDisplay(MutablePoint p)
{
p.X = 100; // 改的是副本
Console.WriteLine(p); // (100, 200)
}
MutateAndDisplay(p2);
Console.WriteLine(p2); // (1, 200) ✅ 原值不受影响
划重点: struct 默认按值传递——方法内部对参数的修改不会影响调用方的原始变量。这与 class 的行为正好相反。
二、重要陷阱:值类型包含引用类型成员
当结构体内部嵌套了引用类型(如 List<string>),复制行为就变得有意思了——值类型实例被复制,但内部的引用类型只复制了引用(地址) 。
public struct TaggedInteger
{
public int Number;
private List<string> tags;
public TaggedInteger(int n)
{
Number = n;
tags = new List<string>();
}
public void AddTag(string tag) => tags.Add(tag);
public override string ToString() => $"{Number} [{string.Join(", ", tags)}]";
}
var n1 = new TaggedInteger(0);
n1.AddTag("A");
Console.WriteLine(n1); // 0 [A]
var n2 = n1; // 复制结构体
n2.Number = 7; // ✅ 只改 n2 的 Number(值类型成员)
n2.AddTag("B"); // ⚠️ tags 是 List<string>,n1 和 n2 共享同一个 List
Console.WriteLine(n1); // 0 [A, B] ← n1 也被影响了!
Console.WriteLine(n2); // 7 [A, B]
- Number(int,值类型)→ 复制时独立
- tags(List<string>,引用类型)→ 复制时只复制引用,两个结构体实例共享同一个列表对象
| 操作 | n1.Number | n1.tags | n2.Number | n2.tags |
|---|---|---|---|---|
| 初始 | 0 | List["A"] | — | — |
| n2 = n1 | 0 | List["A"] | 0 | 同一个List["A"] |
| n2.Number = 7 | 0 | List["A"] | 7 | 同一个List["A"] |
| n2.AddTag("B") | 0 | List["A", "B"] | 7 | 同一个List["A", "B"] |
【提示】 官方明确建议:尽量使用不可变值类型。如果结构体包含引用类型成员,一定要清楚这种"共享引用"的语义——这往往是 bug 的来源。
三、值类型的种类
C# 中的值类型有三种:
3.1 结构类型(struct)
public struct Point { public int X; public int Y; }
3.2 枚举类型(enum)
public enum Color { Red, Green, Blue }
3.3 联合声明(union)
定义一组封闭的事例类型,值可以表示。这是 .NET 6+ 引入的特性。
3.4 可为 null 的值类型(T?)
int? nullableInt = null; // Nullable<int> bool? nullableBool = null; // Nullable<bool>
T? 表示 T 的所有值 + null。不能为 null 的值类型变量无法赋 null。
3.5 泛型约束
// struct 约束:类型参数必须是值类型(struct 或 enum)
public class MyClass<T> where T : struct { }
// System.Enum 约束:类型参数必须是枚举类型
public class MyClass<T> where T : System.Enum { }
四、内置值类型(简单类型)
C# 提供以下内置值类型,也称为简单类型:
| 类别 | 类型 |
|---|---|
| 整型 | byte,sbyte,short,ushort,int,uint,long,ulong,nint,nuint |
| 浮点型 | float,double,decimal |
| 布尔 | bool |
| 字符 | char |
所有简单类型都是结构类型,但与其他结构类型不同,它们允许一些额外操作:
| 操作 | 简单类型 | 自定义结构类型 |
|---|---|---|
| 文字常量 | ✅'A'、2001、12.34m | ❌ |
| const声明 | ✅const int Max = 100; | ❌ |
| 编译期求值 | ✅ 常量表达式在编译时求值 | ❌ |
// 简单类型特有的操作
const int MaxItems = 100; // ✅ struct 是简单类型
const decimal TaxRate = 0.05m; // ✅
// 以下 ❌ 编译错误
public struct Point { public int X; public int Y; }
// const Point Origin = new Point(); // 不能声明结构类型的常量
五、值元组
值元组(ValueTuple<T1, T2>)是值类型,但不是简单类型。
var point = (X: 10, Y: 20); // ValueTuple<int, int>
六、对比:值类型 vs 引用类型
| 维度 | 值类型 | 引用类型 |
|---|---|---|
| 变量内容 | 数据本身 | 数据地址(引用) |
| 赋值行为 | 复制整个实例 | 复制引用(地址) |
| 默认传参 | 按值传递(副本) | 按值传递引用(同上仍是副本) |
| null赋值 | ❌ 不可(除非T?) | ✅ |
| 存储位置 | 栈/内联 | 堆 |
| 继承 | ❌struct不能继承 | ✅class可以 |
| 性能 | 小数据快(无 GC 压力) | 大数据好(引用传递) |
最后: 值类型的行为可以用一句话概括——值类型变量就是数据本身。赋值即复制、传参即复制、包含引用类型成员时要小心"共享引用"。这些规则虽然简单,但组合起来却影响着日常编码中的每一个细节。记住官方建议:优先用不可变值类型,代码会更可靠。
到此这篇关于C# 中值类型的实现示例的文章就介绍到这了,更多相关C# 值类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
C#实现只运行单个实例应用程序的方法(使用VB.Net的IsSingleInstance)
这篇文章主要介绍了C#实现只运行单个实例应用程序的方法,本文使用的是VB.Net的IsSingleInstance方法实现,优于Mutex 和 Process 这两种只运行单个应用程序实例的方法,需要的朋友可以参考下2014-07-07


最新评论