C# 语言核心概念

mindtian 发布于 2025-07-03 317 次阅读


AI 摘要

C# 中的类和结构体,为何一个在堆,一个在栈?背后藏着怎样的性能密码?

一、 字符串

1. 日期格式化

说明符名称示例输出 (zh-CN)描述
t短时间17:58小时:分钟
D长日期2012年10月10日完整年月日
f完整日期时间2012年10月10日 17:58长日期 + 短时间
g常规日期时间2012/10/10 17:58短日期 + 短时间

二、 结构体

1. 值类型 vs 引用类型:

  • 结构是值类型(Value Type): 结构是值类型,它们在栈上分配内存,而不是在堆上。当将结构实例传递给方法或赋值给另一个变量时,将复制整个结构的内容。
  • 类是引用类型(Reference Type): 类是引用类型,它们在堆上分配内存。当将类实例传递给方法或赋值给另一个变量时,实际上是传递引用(内存地址)而不是整个对象的副本。

三、 枚举

1. 声明 enum 变量

enum <enum_name>
{ 
    enumeration list 
};

四、 类

1. 修饰符

访问修饰符含义谁能访问?比喻
public公共的,无限制任何代码(无论内外项目)公司大厅
internal内部的,程序集私有(默认)仅限同一个项目内的代码内部部门
private私有的仅限声明它的那个类自身个人笔记
protected受保护的类自身和它的所有派生类(子类)家族遗传

五、 继承

C# 不支持类的多重继承,但支持接口的多重继承,一个类可以实现多个接口

1. 基本语法

<访问修饰符> class <基类>
{
 ...
}
class <派生类> : <基类>
{
 ...
}

六、 多态性

1. 静态多态性

  • 函数重载
  • 运算符重载

2. 动态多态性

2.1. 关键字

关键字使用位置是否有方法体?子类行为目的
abstract抽象类的方法 (只有声明)必须 override (强制实现)定义一个必须由子类完成的契约,但无默认实现
virtual基类的方法 (提供默认实现)可以选择性地 override (可选重写)提供一个可被子类定制的默认行为
override派生类的方法 (提供新实现)- (本身就是重写行为)为基类的 abstract 或 virtual 方法提供新实现

七、 运算符重载

1. 示例

public static Box operator+ (Box b, Box c)
{
   Box box = new Box();
   box.length = b.length + c.length;
   box.breadth = b.breadth + c.breadth;
   box.height = b.height + c.height;
   return box;
}

八、 接口

接口默认共有,类中方法默认私有

1. 定义接口

接口使用 interface 关键字声明,它与类的声明类似。接口声明默认是 public 的。

/// <summary>
/// 定义了日志记录器的行为规范。
/// 任何实现了此接口的类都能够记录不同级别的消息。
/// </summary>
public interface ILogger
{
    /// <summary>
    /// 记录一条普通信息。
    /// </summary>
    /// <param name="message">要记录的消息文本。</param>
    void LogInfo(string message);

    /// <summary>
    /// 记录一条警告信息。
    /// </summary>
    /// <param name="message">要记录的警告文本。</param>
    void LogWarning(string message);

    /// <summary>
    /// 记录一条错误信息。
    /// </summary>
    /// <param name="message">要记录的错误文本。</param>
    /// <param name="exception">可选的异常对象。</param>
    void LogError(string message, Exception? ex = null); // C# 8.0+ 支持可空引用类型
}

一个接口中的属性声明,规定了实现类必须提供对应的 get 和或 set 访问器。

九、 预处理指令

1. 条件编译

1.1. #define 和 #undef:定义或取消定义符号

1.2. #if#elif#else#endif:条件代码块

2. 错误和警告报告

2.1. #error:生成编译错误

2.2. #warning:生成编译警告

十、 异常处理

1. 四个关键字

  • try:一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。
  • catch:程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。
  • finally:finally 块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。
  • throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。

十一、 文件的输入和输出

C# 文件的输入与输出 | 菜鸟教程

十二、 特性

1. 规定特性

[attribute(positional_parameters, name_parameter = value, ...)]
element

2. 预定义特性

2.1. Obsolete 特性:标记过时成员

产生一个默认警告:

[Obsolete]
public void OldMethod() { /* ... */ }

产生一个带自定义消息的警告

[Obsolete("This method is outdated. Please use NewMethod() instead.")]
public void OldMethod() { /* ... */ }

产生一个编译错误,强制停止使用

[Obsolete("This method has been completely removed. You must use NewMethod().", true)] // 第二个参数为 true
public void VeryOldMethod() { /* ... */ }

2.2. Conditional 特性:条件编译方法

重要规则:

  • 被 [Conditional] 修饰的方法返回值必须是 void 。
  • 它不能是接口方法的实现。

2.3. AttributeUsage 特性:定义你自己的特性如何被使用

规定自己特性的类必须继承System.Attribute

在 C# 中,特性类的名称通常以后缀 Attribute 结尾(例如 AuthorAttribute)。当你在代码中使用这个特性时,可以省略掉 Attribute 这个后缀

规定语法如下:

[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]
  • 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All
  • 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
  • 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

例如:

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]

2.4. Serializable:标记类可以被序列化

序列化是将对象转换为可以存储或传输的形式的过程

2.5. DllImport:用于导入非托管代码中的方法

十三、 反射

1. 查看元数据

System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的特性(attribute)。

System.Reflection.MemberInfo info = typeof(MyClass);

GetCustomAttributes 方法返回的特性数组的顺序是未定义的 (undefined) 。

十四、 属性

类中重写 Tostring 方法时, 任何需要将该类实例转换为字符串来显示的地方,都会自动调用你写的这个版本。

十五、 索引器

基本语法

element-type this[int index] 
{
   // get 访问器
   get 
   {
      // 返回 index 指定的值
   }

   // set 访问器
   set 
   {
      // 设置 index 指定的值 
   }
}

十六、 委托

委托不持有数据,它持有的是对一个 “操作”或“行为”(也就是方法)的引用

“委托的方法需要有跟委托一样的返回类型和参数”

在 C# 中,委托(Delegate) 是一种类型安全的函数指针,它允许将方法作为参数传递给其他方法。

C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变。

声明委托的语法

public delegate <return type> <delegate-name> <parameter list>

中文格式

public delegate 返回类型 委托名(参数类型 参数名, ...);

十七、 事件

事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。

C# 中使用事件机制实现线程间的通信。

关键点:

  • 声明委托:定义事件将使用的委托类型。委托是一个函数签名。
  • 声明事件:使用 event 关键字声明一个事件。
  • 触发事件:在适当的时候调用事件,通知所有订阅者。
  • 订阅和取消订阅事件:其他类可以通过 += 和 -= 运算符订阅和取消订阅事件。

1. 声明事件

声明该事件的委托类型

public delegate void BoilerLogHandler(string status);

声明事件本身

// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;

十八、 多线程

1. 创建线程

ThreadStart childref = new ThreadStart(CallToChildThread);
Console.WriteLine("In Main: Creating the Child thread");
Thread childThread = new Thread(childref);
childThread.Start();

2. 销毁线程

Abort() 方法用于销毁线程。

十九、 泛型

public T Add<T>(T a, T b)
{
    // 将 a 和 b 转换为 dynamic 类型
    // 运行时,DLR (动态语言运行时) 会检查 a 和 b 的真实类型
    // 如果它们确实支持 + 运算符,就执行,否则抛出异常。
    dynamic dynA = a;
    dynamic dynB = b;
    return dynA + dynB;
}

// 用法:
int resultInt = Add(5, 10);             // 正常工作
double resultDouble = Add(3.5, 2.1);   // 正常工作
string resultString = Add("Hello ", "World"); // 正常工作

// Person p1 = new Person(), p2 = new Person();
// Add(p1, p2); // 运行时会抛出异常,因为 Person 类没有定义 + 运算符

二十、 匿名方法

1. lambda 表达式

语法:

(parameters) => expression
// 或
(parameters) => { statement; }

示例: Func sum = (int x, int y) => x + y;

2. 匿名方法

通过使用 delegate 关键字创建委托实例来声明的。

语法:delegate(parameters) { statement; }

二十一、 不安全代码

当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。不安全代码或非托管代码是指使用了指针变量的代码块。

1. 编译不安全代码

在命令行中输入csc /unsafe prog1.cs

二十二、 var 和 dynamic 关键字

1. var 隐式类型局部变量

1.1. 规则

  • 必须在声明时初始化
  • 不能用null初始化
  • 只能用于局部变量

1.2. 什么时候用

  • 使用 new 关键字创建实例时

二十三、 特性 Attribute

1. 使用场景

  • 数据注解特性
    • [Requrired] 用于标记数据字段为必填项
    • [Stringlength] 用于限制字符串的最大长度
    • [Range] 用于指定数值的范围
    • [EmailAddress] 用于验证电子邮箱地址的格式
  • 验证机制
    • 使用 Validator 类可以对数据模型进行验证。如果模型不符合验证规则,Validator 将抛出异常

2. 日志记录与审查

  • 自定义日志记录特性
  • 使用AOP框架实现日志记录

3. 特性运行时的处理

  • 使用反射获取特性
  • 特性和依赖注入

4. 高级应用

  • 特性与代码生成
  • 特性与元数据扩展