Skip to main content

语法基础

本篇文章重点会放在语法的细节上,不对语法基础做过多解释。

数值类型

整型数值类型

类型描述大小(字节)范围默认值CLS 兼容
sbyte有符号 8 位整数1-128 到 1270
byte无符号 8 位整数10 到 2550
short有符号 16 位整数2-32,768 到 32,7670
ushort无符号 16 位整数20 到 65,5350
int有符号 32 位整数4-2,147,483,648 到 2,147,483,6470
uint无符号 32 位整数40 到 4,294,967,2950
long有符号 64 位整数8-9,223,372,036,854,775,808 到 9,223,372,036,854,775,8070
ulong无符号 64 位整数80 到 18,446,744,073,709,551,6150
nint有符号平台相关整数4 或 8与指针相同(32 位进程为 32 位,64 位进程为 64 位)0
nuint无符号平台相关整数4 或 8与指针相同(32 位进程为 32 位,64 位进程为 64 位)0

注意char 类型本质上是无符号 16 位整数(0~65535),但用于表示 Unicode 字符,不应用于算术运算。

平台相关类型 nintnuint

  • 它们的大小取决于运行进程的位数(32 位或 64 位)。
  • 常用于与指针运算、互操作或需要平台自然整数大小的场景。
  • 在 32 位系统上为 32 位,64 位系统上为 64 位。

整数字面量默认是 int,但可以通过后缀指定类型:

后缀示例类型
123int
uU123Uuint
lL123Llong
uluLUlUL123ULulong

还可以使用十六进制(0x)和二进制(0b)表示法:

int hex = 0x7F;      // 127
uint bin = 0b1010; // 10

隐式转换:从范围小的类型到范围大的类型(如 int → long)会自动进行,不会丢失数据。

显式转换:从大范围到小范围可能丢失数据,必须使用强制转换。


long big = 300;
int small = (int)big; // 显式转换,可能溢出

checked 上下文:可检测溢出,默认在非 checked 上下文中溢出会截断,checked 上下文中会抛出 OverflowException

checked
{
int max = int.MaxValue;
int result = max + 1; // 抛出 OverflowException
}

浮点类型

类型描述大小(字节)精度(有效数字)范围(近似)后缀.NET 类型
float单精度浮点数4~6-9 位±1.5e-45 到 ±3.4e38f/FSystem.Single
double双精度浮点数8~15-17 位±5.0e-324 到 ±1.7e308d/DSystem.Double
decimal高精度十进制数(适合财务)1628-29 位有效数字±1.0e-28 到 ±7.9e28m/MSystem.Decimal

decimal类型范围较小,精度较高:

double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);

decimal c = 1.0M;
decimal d = 3.0M; // 指示常量应该用decimal类型
Console.WriteLine(c / d);

不带后缀的浮点数字面量默认为 double。

使用后缀明确指定类型:

  • float:f 或 F(例如 3.14f)

  • decimal:m 或 M(例如 19.99m)

  • double:可省略,也可使用 d/D(如 3.14d)

double d1 = 3.14;      // double
float f1 = 3.14f; // float
decimal m1 = 3.14m; // decimal

内置类型转换

隐式转换:

  • float → double(因为 double 范围更大、精度更高)

  • float 或 double 不能隐式转换为 decimal(可能丢失精度)

源类型目标类型
sbyteshortintlongfloatdoubledecimal
byteshortushortintuintlongulongfloatdoubledecimal
shortintlongfloatdoubledecimal
ushortintuintlongulongfloatdoubledecimal
intlongfloatdoubledecimal
uintlongulongfloatdoubledecimal
longfloatdoubledecimal
ulongfloatdoubledecimal
charushortintuintlongulongfloatdoubledecimal

显式转换:其他类型转换为 float/double/decimal 需要使用强制转换,可能引起精度损失或溢出。

double d = 3.14;
float f = (float)d; // 显式转换,可能精度损失
decimal m = (decimal)d; // 显式转换,double 转 decimal 需要显式
源类型目标类型
longint、uint、short、ushort、byte、sbyte、char
ulonglong、int、uint、short、ushort、byte、sbyte、char
float任何整型、decimal
double任何整型、float、decimal
decimal任何整型、float、double

类型系统

值类型vs引用类型:

特性值类型引用类型
存储位置栈上(或作为其他对象的一部分内联)堆上,变量持有引用
赋值行为复制整个值复制引用,多个变量可指向同一对象
默认值所有位为零(例如 0falsenull
继承隐式密封,不能派生,但可实现接口支持继承和多态
内存释放超出作用域自动释放由垃圾回收器回收
典型示例结构体、枚举、基本数值类型类、接口、委托、数组、字符串
  1. 类(class
  • 引用类型,支持继承、多态、封装。
  • 可以包含字段、属性、方法、事件等。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
  1. 结构体(struct)

值类型,适合轻量级对象。

不支持继承(但可实现接口)。

常用于表示简单数据,如 Point、Rectangle。

public struct Point
{
public int X;
public int Y;
}
  1. 记录(record)

    引用类型(record class)或值类型(record struct)。

    默认提供基于值的相等性、非破坏性修改(with)和简洁语法。

public record Person(string Name, int Age);
public record struct Point(int X, int Y);
tip

想象你在填一张快递单:收件人、地址、电话。这些信息一旦填好,就不希望被随意修改(不可变性)。如果你需要修改,通常会重新填一张新单子。另外,判断两张快递单是否相同,是看上面的信息是否一致(值相等),而不是看是不是同一张纸(引用相等)。

你可以把它看作一种数据容器,它自动为你实现了很多“数据类”通常需要手写的功能(比如值相等性比较、复制、解构等)。

C++中没有直接对应的“记录”,但可以用struct(值类型)或不可变类来模拟。例如,定义一个只有const成员的类,并重载operator==来实现值比较,再实现一个“克隆”方法。C#的记录把这些都自动化了.

  1. 枚举(enum)

    值类型,用于定义一组命名常量。

    默认底层类型为 int,可指定其他整型。

public enum Color
{
Red,
Green,
Blue
}
  1. 接口(interface)

    定义契约,不包含实现。

    类和结构体可实现多个接口。

public interface IMovable
{
void Move(int x, int y);
}
tip

接口(Interface)可以理解为一纸合同或能力证书。它只规定“能做什么”(比如“这个物体可以飞行”),但不规定“怎么做”(比如“用翅膀飞”还是“喷气飞”)。任何类只要签署了这份合同(即实现接口),就必须提供具体的做法。

对比C++,C++中没有专门的“接口”关键字,但可以通过只包含纯虚函数的抽象基类来模拟接口:


class IFlyable { // 相当于C#的接口
public:
virtual void Fly() = 0; // 纯虚函数,无实现
virtual ~IFlyable() {} // 虚析构
};

class Bird : public IFlyable {
public:
void Fly() override { /* 翅膀飞 */ }
};

class Airplane : public IFlyable {
public:
void Fly() override { /* 引擎飞 */ }
};
  1. 委托(delegate)

    引用类型,用于封装方法,类似于函数指针。

    事件基于委托实现。

// 定义一个委托,可以指向任何返回 int、接受两个 int 参数的方法
public delegate int MyDelegate(int x, int y);
MyDelegate del1 = new MyDelegate(Add); // 指向静态方法
MyDelegate del2 = new MyDelegate(new Program().Multiply); // 指向实例方法
int result = del1(3, 5); // 输出 8
// 多播委托
MyDelegate del = Add;
del += Multiply; // 增加一个方法
del(2, 3); // 先执行 Add(2,3) -> 5,再执行 Multiply(2,3) -> 6
tip

想象你去餐厅点餐,你把想吃的菜写在点餐单上,然后交给服务员。服务员根据这张单子去后厨让厨师做菜。这里的“点餐单”就像委托——它记录了你要执行的操作(吃什么菜),而服务员就是调用委托的代码,后厨厨师就是实际干活的方法。

特殊类型与概念

  1. 可空类型(Nullable Types)

    值类型不能为 null,但可通过 Nullable<T> 或 T? 语法使其可为空。

    引用类型默认可为空(C# 8.0 引入可空引用类型上下文)。

int? nullableInt = null;           // 可空值类型
string? nullableString = null; // 可空引用类型(需启用可空上下文)
  1. 泛型(Generics)

允许定义类型参数,提高代码复用和类型安全。

可用于类、结构、接口、委托和方法。

public class List<T>
{
public void Add(T item) { ... }
}
// 具体一点的使用
public class Box<T>
{
private T content;

public void Put(T item) => content = item;
public T Get() => content;
}

// 使用时指定具体类型
Box<int> intBox = new Box<int>();
intBox.Put(123);
int value = intBox.Get(); // 直接得到 int,无需转换

Box<string> stringBox = new Box<string>();
stringBox.Put("Hello");
string text = stringBox.Get();
warning

C# 泛型与C++ 的Template不同,其实例化时机是在运行时,Template实例化是在编译时。

  1. 匿名类型

    由编译器自动生成的只读类型,用于临时存储一组属性。

    常用于 LINQ 查询结果。

var person = new { Name = "Alice", Age = 30 };
  1. 元组(Tuples) 轻量级数据结构,可包含多个不同类型的元素。 支持命名元素和解构。
var tuple = (Name: "Alice", Age: 30);
Console.WriteLine(tuple.Name);
  1. 指针类型

    在不安全上下文中使用,用于直接操作内存。

    使用 * 和 & 操作符。

unsafe
{
int* ptr = &someVariable;
}
  1. 装箱和拆箱

    装箱:值类型转换为 object 或接口类型,在堆上分配对象。

    拆箱:将装箱的对象转换回值类型。

int i = 123;
object obj = i; // 装箱
int j = (int)obj; // 拆箱

类型系统的高级特性

  1. 类型推断(var)

    编译器根据初始化表达式推断变量类型。

    减少冗余,但可能降低可读性(需合理使用)。

var name = "Alice";  // 推断为 string
var numbers = new int[] { 1, 2, 3 };
  1. 模式匹配

    提供更灵活的类型检查和转换语法。

    支持类型模式、属性模式、位置模式等。

object obj = "Hello";
if (obj is string s)
{
Console.WriteLine(s.Length);
}

// switch 表达式
string result = obj switch
{
int i => $"整数 {i}",
string str => $"字符串 {str}",
_ => "其他"
};
  1. 可空引用类型(C# 8.0+)

    帮助开发者避免空引用异常。

    通过注解 ? 表示可为空,编译器提供静态分析。

#nullable enable
string? maybeNull = null; // 可为空
string notNull = "hello"; // 不可为空
  1. 扩展方法

    允许向现有类型添加新方法,而无需修改类型定义。

    定义在静态类中的静态方法,第一个参数使用 this 关键字。

public static class StringExtensions
{
public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
}
  1. 匿名方法和 Lambda 表达式

    用于创建内联函数,常用于委托和 LINQ。

Func<int, int> square = x => x * x;

类型安全与性能

静态类型检查:编译时捕获类型错误。

动态类型(dynamic):绕过编译时检查,运行时解析,可能带来性能开销和运行时错误。

泛型:避免装箱和拆箱,提高性能并保持类型安全。

值类型:减少堆分配和垃圾回收压力。

示例:综合展示类型系统

using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
// 值类型
int age = 30;
Point p1 = new Point(10, 20);
Color color = Color.Red;

// 引用类型
Person person = new Person("Alice", 30);
object obj = person;

// 可空类型
int? maybeNumber = null;

// 泛型
List<string> names = new List<string> { "Alice", "Bob" };

// 匿名类型
var product = new { Name = "Laptop", Price = 999.99 };

// 元组
var tuple = (X: 10, Y: 20);

// 模式匹配
object data = "Hello";
if (data is string s)
{
Console.WriteLine($"字符串长度: {s.Length}");
}

// 装箱拆箱
int i = 42;
object boxed = i; // 装箱
int unboxed = (int)boxed; // 拆箱

// 使用扩展方法
string text = "test";
Console.WriteLine(text.IsNullOrEmpty());
}
}

// 自定义类型
public record Person(string Name, int Age);
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
}
public enum Color { Red, Green, Blue }

// 扩展方法
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
}