lin

返回

C# 语法知识点#

C# 是微软基于 .NET 平台开发的强类型、面向对象编程语言,广泛应用于桌面应用(WinForms/WPF)、Web 应用(ASP.NET)和游戏开发(Unity)等场景。本文以 WinForms 开发为背景,按基础语法、流程控制、面向对象、常用类库、进阶特性的顺序整理常用语法。

注意:C# 严格区分大小写,Student 与 student 是两个不同的标识符;每条语句以分号 ; 结尾。

1. 程序基本结构#

1.1 命名空间与类#

C# 程序由命名空间(namespace)组织,命名空间下定义类(class),类中包含字段、属性、方法等成员。

using System;                 // 引入命名空间

namespace StudentManagerPro   // 定义命名空间
{
    class Program             // 定义类
    {
        static void Main()    // 程序入口方法
        {
            Console.WriteLine("Hello");
        }
    }
}
csharp

1.2 注释#

// 单行注释

/* 多行注释
   可跨越多行 */

/// <summary>
/// XML 文档注释,描述类和方法的用途,可被 IDE 智能提示读取
/// </summary>
csharp

2. 变量与数据类型#

2.1 变量与常量#

int age = 20;              // 声明并赋值
string name;               // 先声明
name = "张三";             // 后赋值
const double PI = 3.14159; // 常量,声明后不可修改
csharp

2.2 值类型与引用类型#

C# 数据类型分为两大类:

  • 值类型(Value Type):变量直接存储数据,赋值时复制副本,互不影响。包括 int、double、bool、char、struct 等。
  • 引用类型(Reference Type):变量存储对象的引用(地址),赋值时复制引用,两个变量指向同一个对象。包括 string、数组、类(class)等。
int a = 10;
int b = a;      // b 是 a 的副本
b = 20;         // a 仍为 10

int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1;   // arr2 与 arr1 指向同一个数组
arr2[0] = 100;       // arr1[0] 也随之变为 100
csharp

2.3 常用数据类型#

类型说明示例
int32 位整数int age = 20;
long64 位整数long id = 123456789012L;
double双精度浮点数double score = 95.5;
decimal高精度小数,用于金额计算decimal price = 9.9M;
bool布尔值bool isPassed = true;
char单个字符char gender = '男';
string字符串string name = "张三";
DateTime日期时间DateTime birthday = new DateTime(2000, 1, 15);

注意:string 虽然是引用类型,但具有不可变性(immutable),每次修改都会生成新的字符串对象。

2.4 var 隐式类型#

var 由编译器根据初始值自动推断类型,声明时必须初始化。

var age = 20;              // 推断为 int
var name = "张三";         // 推断为 string
var list = new List<int>();
csharp

注意:var 不是动态类型,编译后类型即固定,之后不能再赋其他类型的值。

2.5 可空类型#

值类型默认不能为 null,在类型后加 ? 可声明可空类型。

int? classId = null;
if (classId.HasValue)
{
    int id = classId.Value;
}

// ?? 空合并运算符:左边为 null 时取右边的默认值
int id2 = classId ?? 0;
csharp

2.6 类型转换#

// 隐式转换:小范围到大范围,自动完成
int a = 10;
double b = a;

// 显式转换(强制转换):大范围到小范围,可能丢失精度
double c = 9.8;
int d = (int)c;                  // d = 9,小数部分被截断

// Parse:字符串转数值,格式错误会抛出异常
int e = int.Parse("123");

// TryParse:转换失败返回 false,不抛异常(推荐)
if (int.TryParse("123", out int f))
{
    // 转换成功,f = 123
}

// Convert:通用转换,null 转数值时返回 0
int g = Convert.ToInt32(reader["Age"]);

// ToString:任意类型转字符串
string s = 123.ToString();
csharp

2.7 枚举(Enum)#

枚举将一组相关的常量命名,避免在代码中使用难以理解的”魔法值”。

public enum Gender
{
    男,
    女
}

Gender g = Gender.男;
csharp

2.8 装箱与拆箱#

装箱(Boxing)是将值类型转换为引用类型(object)的过程,拆箱(Unboxing)则相反。

int a = 10;
object obj = a;     // 装箱:值类型 → object
int b = (int)obj;   // 拆箱:object → 值类型,必须强制转换为原类型
csharp

注意:装箱拆箱涉及堆内存分配,频繁发生会影响性能。使用泛型集合(如 List<int>)代替非泛型集合(如 ArrayList)可避免这一问题。

2.9 相等比较(== 与 Equals)#

类型== 的行为Equals() 的行为
值类型比较值比较值
string比较内容(运算符被重载)比较内容
其他引用类型比较引用(是否同一对象)默认比较引用,可重写为比较内容
int a = 10, b = 10;
Console.WriteLine(a == b);       // True,值比较

string s1 = "abc";
string s2 = "abc";
Console.WriteLine(s1 == s2);     // True,string 的 == 比较内容

Student stu1 = new Student();
Student stu2 = stu1;             // 指向同一对象
Student stu3 = new Student();
Console.WriteLine(stu1 == stu2); // True
Console.WriteLine(stu1 == stu3); // False,两个不同的对象
csharp

自定义类可重写 Equals 方法实现按内容比较(如按学号判断两个 Student 是否代表同一人)。

3. 运算符#

分类运算符
算术+ - * / %(取余)
比较== != > >= < <=
逻辑&&(并且)||(或者)!(取反)
赋值= += -= *= /=
自增自减++ --
条件(三元)条件 ? 值1 : 值2
int result = 10 % 3;                           // 取余,结果为 1
string level = age >= 18 ? "成年" : "未成年";  // 条件运算符
csharp

注意:&& 和 || 具有短路特性:左边已能确定结果时,右边的表达式不再执行。事件触发前的判空 if (handler != null && handler()) 正是利用了这一特性。

4. 流程控制#

4.1 条件语句(if / switch)#

if (age >= 18)
{
    // ...
}
else if (age >= 12)
{
    // ...
}
else
{
    // ...
}

// switch:按固定值分支
switch (classId)
{
    case 1:
        Console.WriteLine("软件1班");
        break;
    case 2:
        Console.WriteLine("软件2班");
        break;
    default:
        Console.WriteLine("未知班级");
        break;
}
csharp

注意:C# 的 switch 中每个 case 必须以 break 等跳转语句结束,不能像 JavaScript 那样自动贯穿到下一个 case。

4.2 循环语句#

// for:已知循环次数时使用
for (int i = 0; i < 10; i++) { }

// foreach:遍历集合,日常开发最常用
foreach (Student stu in studentList)
{
    Console.WriteLine(stu.StudentName);
}

// while:先判断后执行
while (reader.Read()) { }

// do-while:先执行一次再判断
do { } while (condition);
csharp

4.3 跳转语句#

  • break:立即退出当前循环。
  • continue:跳过本次循环的剩余部分,直接进入下一次循环。
  • return:结束当前方法并返回结果。

5. 数组与集合#

5.1 数组(Array)#

数组长度在创建时确定,之后不可改变。

int[] scores = new int[5];            // 长度为 5 的数组
string[] names = { "张三", "李四" };  // 声明同时初始化
int first = scores[0];                // 下标从 0 开始
int len = scores.Length;              // 数组长度
csharp

5.2 List<T>(列表)#

长度可变的泛型集合,T 为元素类型,是日常开发中最常用的集合。

List<Student> list = new List<Student>();
list.Add(new Student());          // 添加元素
list.Remove(stu);                 // 删除元素
int count = list.Count;           // 元素个数
Student first = list[0];          // 按下标访问
bool exist = list.Contains(stu);  // 判断是否包含
csharp

5.3 Dictionary<K,V>(字典)#

键值对(Key-Value)集合,通过键快速查找对应的值。

Dictionary<int, string> classMap = new Dictionary<int, string>();
classMap.Add(1, "软件1班");
string name = classMap[1];             // 按键取值
bool hasKey = classMap.ContainsKey(1); // 判断键是否存在
csharp

5.4 泛型(Generics)#

泛型允许在定义类、接口、方法时不指定具体类型,改用类型参数 T 占位,使用时再确定具体类型,兼顾代码复用与类型安全。常用的 List<T>、Dictionary<K,V> 中的 T、K、V 都是类型参数。

// 定义泛型类:同一套逻辑适配不同类型
public class Box<T>
{
    public T Content { get; set; }
}

// 使用时指定具体类型
Box<int> intBox = new Box<int>() { Content = 10 };
Box<string> strBox = new Box<string>() { Content = "文本" };
csharp

相比使用 object,泛型无需装箱拆箱与强制类型转换,且类型错误在编译期即可发现。

5.5 LINQ 基础查询#

LINQ(Language Integrated Query)为集合提供类似 SQL 的查询能力,常用方法如下:

方法作用类比 SQL
Where(...)条件筛选where
Select(...)投影,从元素中取出指定字段select 列名
FirstOrDefault(...)取第一个满足条件的元素,无则返回默认值top 1
Any(...)是否存在满足条件的元素count(*) > 0
OrderBy(...) / OrderByDescending(...)升序 / 降序排序order by
Count(...)统计满足条件的元素个数count(*)
// 筛选年龄大于 20 的学员
List<Student> result = studentList.Where(s => s.Age > 20).ToList();

// 只取姓名,组成新集合
List<string> names = studentList.Select(s => s.StudentName).ToList();

// 查找学号为 1001 的学员,找不到返回 null
Student stu = studentList.FirstOrDefault(s => s.StudentId == 1001);

// 判断是否存在 2 班的学员
bool exists = studentList.Any(s => s.ClassId == 2);
csharp

注意:Where、Select 返回的是延迟执行的查询,调用 ToList() 才会立即执行并生成新集合。

6. 字符串处理#

6.1 常用方法#

方法作用示例
Length返回长度"abc".Length → 3
ToUpper() / ToLower()大小写转换"abc".ToUpper() → “ABC”
Trim()去除两端空格" a ".Trim() → “a”
Substring(起点, 长度)截取子串"abcdef".Substring(1, 3) → “bcd”
Replace(旧值, 新值)替换"aab".Replace("a", "x") → “xxb”
Contains(s)是否包含子串"abc".Contains("b") → true
IndexOf(s)子串位置,未找到返回 -1"abc".IndexOf("c") → 2
Split(分隔符)拆分为字符串数组"a,b,c".Split(',')
string.Join(分隔符, 集合)将集合拼接为字符串string.Join(",", arr)
string.IsNullOrEmpty(s)是否为 null 或空字符串输入校验常用

注意:与 SQL 的 substring 不同,C# 中字符串下标从 0 开始。

6.2 字符串格式化#

// string.Format:占位符从 0 开始编号
string s1 = string.Format("姓名:{0},年龄:{1}", name, age);

// $ 字符串插值(C# 6 及以上,推荐):直接在字符串中嵌入变量
string s2 = $"姓名:{name},年龄:{age}";
csharp

6.3 StringBuilder#

string 不可变,循环内频繁拼接会产生大量临时对象,影响性能。该场景应使用 StringBuilder:

StringBuilder sb = new StringBuilder();
sb.Append("insert into Students ");
sb.Append("values(...)");
string sql = sb.ToString();
csharp

7. 面向对象#

7.1 类与对象#

类(class)是对象的模板,通过 new 关键字创建对象(实例)。

// 定义类
public class Student
{
    // 属性
    public int StudentId { get; set; }
    public string StudentName { get; set; }
    public int Age { get; set; }

    // 方法
    public string GetInfo()
    {
        return $"姓名:{StudentName},年龄:{Age}";
    }
}

// 创建对象
Student stu = new Student();
stu.StudentName = "张三";
stu.Age = 20;

// 对象初始化器:创建对象的同时为属性赋值
Student stu2 = new Student()
{
    StudentName = "李四",
    Age = 22
};
csharp

7.2 属性(Property)#

属性是对字段的封装,通过 get / set 访问器控制读写。

// 自动属性:编译器自动生成背后的私有字段
public string StudentName { get; set; }

// 完整写法:可在访问器中加入校验等逻辑
private int age;
public int Age
{
    get { return age; }
    set
    {
        if (value < 0 || value > 150)
        {
            throw new Exception("年龄不合法");
        }
        age = value;
    }
}

// 受限访问:外部只读,仅类内部可写
public int StudentId { get; private set; }
csharp

7.3 方法(Method)#

// 返回值类型 方法名(参数列表)
public int Add(int a, int b)
{
    return a + b;
}

// void 表示无返回值
public void Show() { }

// 方法重载(Overload):方法名相同、参数列表不同,编译器按实参自动匹配
public bool IsIdNoExistd(string idNo) { }
public bool IsIdNoExistd(string idNo, int studentId) { }

// 可选参数:带默认值的参数,调用时可省略
public void Search(string name = "", int age = 0) { }
csharp

7.4 参数修饰符(ref / out / params)#

  • ref:按引用传参,方法内的修改会反映到调用方的变量上,传参前必须先赋值。
  • out:按引用传参,用于让方法带回多个结果,传参前不必赋值,但方法返回前必须为其赋值。
  • params:可变参数,允许传入任意个数的同类型实参,方法内按数组处理。
// ref:交换两个变量的值
public void Swap(ref int a, ref int b)
{
    int temp = a; a = b; b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y);       // 调用处也要加 ref,执行后 x = 2,y = 1

// out:一个方法同时带回"是否成功"和"结果"两个值
public bool TryDivide(int a, int b, out int result)
{
    if (b == 0)
    {
        result = 0;
        return false;
    }
    result = a / b;
    return true;
}
if (TryDivide(10, 3, out int r))
{
    // 除法成功,r = 3
}

// params:接受任意个数的参数
public int Sum(params int[] numbers)
{
    int sum = 0;
    foreach (int n in numbers) sum += n;
    return sum;
}
Sum(1, 2, 3, 4);          // 直接传多个值,无需先组装数组
csharp

前文 int.TryParse("123", out int f) 中的 out 正是利用了这一机制:方法返回值表示转换是否成功,转换结果通过 out 参数带出。

7.5 构造函数(Constructor)#

构造函数在 new 对象时自动执行,用于初始化。方法名与类名相同,没有返回值类型。

public class Student
{
    public Student() { }                   // 无参构造函数

    public Student(string name, int age)   // 带参构造函数
    {
        StudentName = name;
        Age = age;
    }
}

Student stu = new Student("张三", 20);
csharp

7.6 静态成员(static)#

static 成员属于类本身而非某个对象,通过类名直接访问,无需创建实例。

public class SQLHelper
{
    public static string connString = "...";

    public static int Update(string sql) { }
}

// 调用方式:类名.成员
SQLHelper.Update(sql);
csharp
对比项实例成员静态成员
归属每个对象各一份全类共享一份
调用方式对象.成员类名.成员
内存分配随对象创建而分配程序启动时分配,常驻内存

注意:静态方法中不能直接访问实例成员,因为静态方法执行时不存在任何具体对象。

7.7 访问修饰符#

修饰符可访问范围
public任何地方
private仅当前类内部(类成员的默认级别)
protected当前类及其子类
internal同一程序集(项目)内

7.8 继承(Inheritance)#

子类继承父类的成员,实现代码复用。C# 只支持单继承,即一个类只能有一个直接父类。

// 父类
public class Person
{
    public string Name { get; set; }
    public void Eat() { }
}

// 子类:冒号表示继承
public class Student : Person
{
    public int StudentId { get; set; }
}

Student stu = new Student();
stu.Name = "张三";   // 继承自 Person
stu.Eat();           // 继承自 Person
csharp

base 关键字用于在子类中访问父类成员:

public Student(string name) : base(name) { }   // 调用父类构造函数
csharp

注意:使用 sealed 修饰的类不能被继承,如 sealed class Student。string 就是典型的密封类。

7.9 多态(virtual / override)#

父类方法用 virtual 声明为虚方法,子类用 override 重写。通过父类引用调用该方法时,运行时根据对象的实际类型决定执行哪个版本。

public class Person
{
    public virtual void SayHello()
    {
        Console.WriteLine("我是人");
    }
}

public class Student : Person
{
    public override void SayHello()
    {
        Console.WriteLine("我是学生");
    }
}

Person p = new Student();
p.SayHello();   // 输出"我是学生",由实际对象类型决定,而非引用类型
csharp

7.10 抽象类(abstract)#

抽象类不能被实例化,专用于作为其他类的公共基类。用 abstract 修饰的方法没有方法体,子类必须重写实现。

public abstract class Animal
{
    public string Name { get; set; }

    public void Eat()                     // 普通方法:包含实现,子类直接继承
    {
        Console.WriteLine("吃东西");
    }

    public abstract void MakeSound();     // 抽象方法:只有签名,子类必须实现
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪");
    }
}

// Animal a = new Animal();   // 错误:抽象类不能实例化
Animal dog = new Dog();       // 正确:父类引用指向子类对象
csharp

注意:包含抽象方法的类必须声明为抽象类;抽象类中也可以只有普通方法而不含抽象方法。

7.11 类型判断(is / as)#

  • is:判断对象是否为指定类型(或其子类),返回 bool
  • as:尝试将对象转换为指定类型,失败时返回 null,不抛异常
Person p = new Student();

if (p is Student)                 // 类型判断
{
    Student s = (Student)p;       // 强制转换
}

Student s2 = p as Student;        // 转换失败时 s2 为 null
if (s2 != null) { }

// C# 7 及以上支持模式匹配,判断与转换一步完成
if (p is Student s3)
{
    // 可直接使用 s3
}
csharp

7.12 接口(Interface)#

接口只定义成员签名,不包含实现。类实现接口后必须实现其全部成员。一个类可以实现多个接口,弥补了单继承的局限。

public interface IStudentService
{
    int AddStudent(Student stu);      // 只有签名,没有方法体
    List<Student> GetStudents();
}

public class StudentService : IStudentService
{
    public int AddStudent(Student stu)
    {
        // 具体实现
    }

    public List<Student> GetStudents()
    {
        // 具体实现
    }
}
csharp

接口与抽象类的区别:

对比项接口抽象类
方法实现不能包含任何实现可包含已实现的方法
继承数量一个类可实现多个接口一个类只能继承一个抽象类
字段不能定义字段可以定义字段

8. 委托与事件#

8.1 委托(Delegate)#

委托是方法的类型化引用,用于将方法作为变量传递、存储和调用,相当于前端的回调函数类型。

// 定义委托类型:约定方法的签名(参数与返回值)
public delegate void DataHandler(string data);

// 符合该签名的方法
public void ReceiveData(string data)
{
    Console.WriteLine("收到: " + data);
}

// 委托变量指向方法(注意方法名后不加括号,是取引用而非调用)
DataHandler handler = ReceiveData;
handler("张三");   // 调用委托 = 调用 ReceiveData("张三")
csharp

委托支持多播:通过 += 挂载多个方法,调用时按顺序依次执行。

8.2 事件(Event)#

事件是对委托的封装:外部只能通过 += 订阅、-= 取消订阅,不能直接触发;触发逻辑封装在所属类内部。

public class FrmAddStudent
{
    // 声明事件
    public static event DataHandler StudentChanged;

    // 触发方法:只有所属类内部可以触发事件
    public static void NotifyStudentChanged(string name)
    {
        if (StudentChanged != null)   // 触发前必须判空
        {
            StudentChanged(name);
        }
    }
}

// 订阅(在其他窗体中)
FrmAddStudent.StudentChanged += OnStudentChanged;

// 取消订阅(窗体关闭时必须执行,防止内存泄漏)
FrmAddStudent.StudentChanged -= OnStudentChanged;
csharp

8.3 Lambda 表达式#

Lambda 表达式是匿名方法的简写形式,=> 左边是参数,右边是方法体。

// 匿名方法写法
list.Find(delegate(Student s) { return s.Age > 20; });

// Lambda 简写
list.Find(s => s.Age > 20);

// 订阅事件
btnSend.Click += (sender, e) => { MessageBox.Show("点击了"); };
csharp

9. 异常处理#

9.1 try-catch-finally#

try
{
    // 可能出错的代码
    int result = Convert.ToInt32(input);
}
catch (FormatException ex)
{
    // 捕获特定类型的异常
    MessageBox.Show($"格式错误:{ex.Message}");
}
catch (Exception ex)
{
    // 捕获所有异常,放在最后
    MessageBox.Show($"操作失败:{ex.Message}");
}
finally
{
    // 无论是否出错都会执行,通常用于释放资源
    conn.Close();
}
csharp

注意:多个 catch 块按从上到下的顺序匹配,具体异常类型应写在通用 Exception 之前。

9.2 throw 抛出异常#

// 包装原始异常后重新抛出,为上层调用者提供更明确的错误信息
catch (Exception ex)
{
    throw new Exception("添加学员失败,数据库操作异常:" + ex.Message);
}
csharp

10. 资源释放(using 语句)#

对于实现了 IDisposable 接口的资源(数据库连接、文件流等),using 语句可确保使用完毕后自动释放,等价于在 try-finally 中调用 Dispose()。

using (SqlConnection conn = new SqlConnection(connString))
{
    conn.Open();
    // 使用连接
}   // 离开 using 作用域时自动调用 conn.Dispose()
csharp

11. 常用类库#

除语法本身外,.NET 基类库(BCL)提供了大量现成功能。以下介绍 WinForms 开发中常用的两类。

11.1 DateTime 常用操作#

成员作用
DateTime.Now当前日期时间
DateTime.Today当前日期(时间部分为 0)
Year / Month / Day / Hour / Minute / Second取年、月、日、时、分、秒
AddDays(n) / AddMonths(n) / AddYears(n)日期加减
ToString("格式")按指定格式输出字符串
DateTime.Parse(s) / DateTime.TryParse(s, out d)字符串转日期
DateTime now = DateTime.Now;
string s1 = now.ToString("yyyy-MM-dd");          // 2026-10-17
string s2 = now.ToString("yyyy-MM-dd HH:mm:ss"); // 2026-10-17 18:30:00
DateTime weekLater = now.AddDays(7);             // 7 天后

// 计算周岁
int age = DateTime.Now.Year - birthday.Year;
csharp

注意:DateTime 与 SQL Server 的 datetime 类型对应,写入数据库时直接以 DateTime 对象作为参数传递即可,无需手动转换为字符串。

11.2 文件读写(File)#

System.IO 命名空间下的 File 类提供操作文件的静态方法。

using System.IO;

// 写入文本(覆盖原内容)
File.WriteAllText("log.txt", "第一行内容");

// 追加文本
File.AppendAllText("log.txt", "追加一行");

// 读取全部文本
string content = File.ReadAllText("log.txt");

// 按行读取为数组
string[] lines = File.ReadAllLines("log.txt");

// 判断文件是否存在
bool exists = File.Exists("log.txt");
csharp
C# 语法知识点
作者 lin
发布于 2026年10月17日

留言

无需登录,留下你的想法。

0
昵称颜色
可见范围