Skip to content

C# - 数据类型

在 C# 中,**数据类型(data type)指定了变量可以保存的数据种类,例如数字、文本或真/假值。作为一种静态类型(statically-typed)且类型安全(type-safe)**的语言,C# 要求你在使用变量之前声明其类型。这有助于在编译时捕获错误,从而产生更健壮、更可靠的软件。

C# 数据类型大致分为两种主要类型:值类型(Value Types)和引用类型(Reference Types)。

值类型直接包含其数据,并且通常存储在**栈(stack)**上,这是用于短期数据的快速内存区域。当你将一个值类型变量赋给另一个变量时,会创建值的副本。

常见的值类型包括:

  • 整型: int、long、byte、short(用于整数)。
  • 浮点型: double、float、decimal(用于带小数的数字)。
  • 其他类型: bool(真/假)、char(单个字符)、enum(一组命名常量)和 struct(用户定义的值类型)。
TypeSize (Bytes)最佳应用场景
int4通用整数(例如:年龄、计数)
long8非常大的整数(例如:数据库 ID)
double8通用浮点数(例如:科学计算)
decimal16高精度财务计算(例如:货币)
int age = 30;
double pi = 3.14159;
decimal price = 19.99m; // `decimal` 类型的后缀 'm'
long population = 8_000_000_000L; // `long` 类型的后缀 'L',`_` 用于提高可读性
bool isComplete = true;
char grade = 'A';

引用类型不直接存储实际数据。相反,它们存储一个引用(或内存地址)指向数据,而数据本身位于**堆(heap)**上,这是一个用于长期对象的内存区域。当你将一个引用类型变量赋给另一个变量时,只复制引用,而不是对象本身。两个变量都将指向同一个对象。

常见的引用类型包括:

  • string:字符序列。
  • object:.NET 中所有类型的最终基类。
  • 数组(Arrays):相同类型元素的集合。
  • 类(Classes)、接口(Interfaces)、委托(Delegates)、记录(Records):用户定义的引用类型。
// s1 持有堆上一个对象的引用。
string s1 = "Hello";
// s2 现在持有该引用的副本,指向同一个对象。
string s2 = s1;
// 注意:`string` 是一个特例;它是不可变的。看似修改 `string` 的操作
// 实际上是创建了一个新的 `string` 对象。
// 一个标准的类示例
var person1 = new Person { Name = "John" };
var person2 = person1; // 两者都引用同一个 `Person` 对象
person2.Name = "Jane";
Console.WriteLine(person1.Name); // 输出:Jane

var 关键字允许编译器从变量的初始化值推断其类型。这可以通过减少冗余使代码更具可读性。

重要提示: var 不意味着变量是动态类型化(dynamic typed)的(像在 JavaScript 中那样)。类型仍然在编译时确定,之后不能更改。它只是语法糖。

var message = "This is a string"; // 编译器推断为 `string`
var count = 100; // 编译器推断为 `int`
var prices = new List<decimal>(); // 编译器推断为 `List<decimal>`

默认情况下,引用类型可以为 null,而值类型不能。C# 提供了安全处理可空性(nullability)的功能。

  • 可空值类型(Nullable Value Types): 要使值类型可空,请在其类型声明后附加一个 ?。例如:int? age = null;。
  • 可空引用类型(Nullable Reference Types - NRTs): C# 的一项现代功能(在新项目中默认启用),可帮助你避免可怕的 NullReferenceException。如果你尝试在未先检查的情况下使用可能为 null 的引用类型,编译器会发出警告。
// 在项目文件中启用 NRTs:<Nullable>enable</Nullable>
string nonNullableName = "World"; // 不能为 `null`
string? nullableName = null; // 可以为 `null`
Console.WriteLine($"Hello, {nonNullableName.ToUpper()}"); // 安全
// 编译器警告:nullableName 在这里可能为 `null`。
// Console.WriteLine($"Hello, {nullableName.ToUpper()}");
// 正确的检查方式:
if (nullableName != null)
{
Console.WriteLine($"Hello, {nullableName.ToUpper()}");
}

将数据从一种类型转换为另一种类型是一项常见的任务。

  • 隐式转换(安全): 当不会丢失数据时自动发生。int 到 double 是安全的。
  • 显式转换(强制类型转换): 当数据可能丢失时需要。你必须使用强制类型转换运算符 (type)。double 到 int 是显式转换,因为小数部分会丢失。
  • 辅助方法(最安全): Convert 类和 TryParse 方法是处理来自外部源(如用户输入或文件)转换的最佳方式。
// 1. 隐式转换
int myInt = 10;
double myDouble = myInt; // 安全,无数据丢失
// 2. 显式转换(强制类型转换)
double anotherDouble = 9.78;
int anotherInt = (int)anotherDouble; // 显式强制类型转换,结果为 9
// 3. 辅助方法(最佳实践)
string userInput = "123";
if (int.TryParse(userInput, out int parsedValue))
{
Console.WriteLine($"成功解析为: {parsedValue}");
}
else
{
Console.WriteLine("无效的数字格式。");
}

指针类型是从 C/C++ 继承的特性。它们存储变量的内存地址。这是一个高级功能,在典型的 C# 应用程序代码中很少使用。 它主要用于对性能要求极高的场景或与非托管代码(如 C++ 库)进行互操作。

警告: 使用指针的代码必须用 unsafe 关键字标记,并且可能需要特定的编译器设置(在 .csproj 文件中添加 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>)。请务必谨慎使用。

public class UnsafeExample
{
public static unsafe void Main()
{
int number = 20;
// `&` 运算符获取变量的内存地址
int* pointerToNumber = &number;
Console.WriteLine($"Value of number: {number}");
Console.WriteLine($"Address of number: {(long)pointerToNumber}");
Console.WriteLine($"Value via pointer: {*pointerToNumber}");
// 使用指针修改值
*pointerToNumber = 30;
Console.WriteLine($"New value of number: {number}");
}
}