核心目标: 理解原理、掌握应用、避开陷阱、应对提问。
值类型 (Value Types) vs 引用类型 (Reference Types)
struct
, enum
, 基本类型如 int
, double
, bool
, char
, decimal
, DateTime
):
System.ValueType
(它继承 System.Object
)。0
/false
/null
(取决于具体类型)。class
, interface
, delegate
, array
, string
):
System.Object
或其它类。null
。string
的特殊性:
Substring
, Replace
, +=
等)都会在堆上创建新字符串对象。 (性能考量:大量字符串操作用 StringBuilder
)。str1 = "hello"; str2 = "hello";
可能指向同一个对象(但 new string("hello".ToCharArray())
不会驻留)。String.Intern()
/ IsInterned()
。=
和传参时,值类型复制值,引用类型复制引用。string
连接性能陷阱:循环内大量 s += "a"
会产生大量中间垃圾字符串。务必用 StringBuilder
!struct
? 小(通常<16字节)、逻辑表示单一值、不可变、不常装箱。否则优先 class
。装箱 (Boxing) 与拆箱 (Unboxing)
object
或该值类型实现的接口类型)。在堆上分配内存,复制值类型数据到其中。
int i = 42; object o = i; // Boxing
int j = (int)o; // Unboxing
InvalidCastException
。
double d = 3.14; object o = d; int i = (int)o; // 运行时错误!
int i = (int)(double)o;
或直接 int i = (int)d;
(避免装箱)List
而非 ArrayList
)、常见错误场景。可空值类型 (Nullable Value Types)
Nullable
或 T?
(如 int?
, DateTime?
)。null
值。常用于数据库交互、可选参数、表示缺失值。public struct Nullable where T : struct { public bool HasValue; public T Value; }
。HasValue
属性。Value
属性 (若 HasValue == false
, 访问 Value
抛 InvalidOperationException
)。??
空合并运算符: 提供默认值。int result = nullableInt ?? -1;
?.
空条件运算符 (C# 6+): 安全访问成员。int? length = customer?.Name?.Length;
(任一环节为 null
则整个表达式结果为 null
)。??
和 ?.
的作用。类型转换 (Type Conversion)
int
-> long
, short
-> int
, 派生类
-> 基类
)。(type)
指定,可能丢失信息或失败 (如 long
-> int
, double
-> int
, object
-> string
, 基类
-> 派生类
)。as
运算符:
null
(不抛出异常)。string s = obj as string; if (s != null) { ... }
is
运算符:
if (obj is string) { ... }
if (obj is string s) { /* 可直接使用变量 s */ }
(同时引入新变量 s
并赋值)。as
vs (type)
强转 (失败时行为不同)、is
模式匹配的便利性、值类型转换的精度丢失问题。类 (Class) vs 结构体 (Struct)
特性 | 类 (class ) |
结构体 (struct ) |
---|---|---|
类型 | 引用类型 | 值类型 |
内存 | 堆 (Heap) | 栈 (Stack) / 嵌入在包含对象中 |
赋值 | 复制引用 (指向同一对象) | 复制整个值 (新副本) |
继承 | 支持单继承 | 不支持继承 (只能实现接口) |
无参构造 | 可显式定义 (若有参构造则需显式) | 不能显式定义 (编译器自动生成) |
字段初始化 | 无强制要求 | 必须在声明时或在构造中初始化所有字段 |
适合场景 | 复杂对象、有行为、有状态、需继承 | 小型、轻量、数据为主、值语义、不可变 |
struct
? (强调值语义、不可变、小尺寸)。readonly struct
(C# 7.2+) 的意义 (强制不可变,优化编译器)。面向对象四大特性
public
: 任何地方可访问。protected
: 本类及其派生类可访问。internal
: 同一程序集(Assembly) 内可访问。默认的类访问级别。protected internal
: protected
或 internal
(取并集)。private
: 仅本类可访问 (默认的成员访问级别)。get
(读取) 和 set
(写入) 访问器。public string Name { get; set; }
(编译器自动生成私有字段)。private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
_name = value;
}
}
public int Id { get; }
(只能在构造函数或初始化器中设置)。get
中返回计算值,无后备字段。:
语法: class DerivedClass : BaseClass { ... }
。base
关键字: 访问基类成员 (如调用基类构造函数 base(...)
, 调用基类方法 base.MethodName()
)。派生类构造函数必须 (显式或隐式) 调用基类构造函数!sealed
关键字: 修饰类,阻止该类被继承;修饰方法 (override 的方法),阻止该方法在派生类中被进一步重写。virtual
): 在基类中声明,表示该方法可以在派生类中被重写。override
): 在派生类中使用,提供基类虚方法的新实现。new
修饰符 (隐藏): 在派生类中使用,声明一个与基类同名的成员 (非虚方法或未重写的虚方法)。这不是重写,只是隐藏基类成员。通过基类引用访问时,调用的是基类版本;通过派生类引用访问时,调用的是派生类新版本。尽量避免使用 new
,易混淆。class Shape { public virtual void Draw() => Console.WriteLine("Shape"); }
class Circle : Shape { public override void Draw() => Console.WriteLine("Circle"); } // 多态
class Square : Shape { public new void Draw() => Console.WriteLine("Square"); } // 隐藏
Shape s1 = new Circle(); s1.Draw(); // Output: "Circle" (多态)
Shape s2 = new Square(); s2.Draw(); // Output: "Shape" (基类方法,隐藏)
Square sq = new Square(); sq.Draw(); // Output: "Square" (派生类新方法)
abstract class
) 和抽象方法 (abstract method
):
abstract
+ 签名),没有实现。必须在非抽象派生类中被 override
。interface
):
:
语法实现接口 (class MyClass : IMyInterface, IAnotherInterface
)。public
,不能有访问修饰符。void IInterface.Method() { ... }
。通过接口引用调用。成员详解
public ClassName(...) { ... }
。初始化对象状态。可重载。静态构造函数 (Static Constructor): static ClassName() { ... }
。初始化静态成员,只执行一次,在首次访问类前自动调用。~ClassName() { ... }
。在 GC 回收对象内存之前调用。无法确定何时调用。主要用于清理非托管资源(但不可靠!)。现代代码优先使用 IDisposable
模式!ref
传递: void Method(ref int x)
. 传递变量的内存地址。方法内对参数 (x
) 的修改直接影响调用方的原始变量。调用时需加 ref
(Method(ref myVar);
)。ref
可用于值类型和引用类型。out
传递: void Method(out int result)
. 用于返回多个值。方法内部必须在返回前为 out
参数赋值。调用时需加 out
(Method(out var output);
)。调用前变量无需初始化。in
传递 (C# 7.2+): void Method(in LargeStruct data)
. 传递对大型值类型的只读引用,避免复制开销。方法内部不能修改 in
参数。调用时通常无需加 in
(编译器优化)。public T this[int index] { get { ... } set { ... } }
。public static Complex operator +(Complex a, Complex b) { ... }
。重载 +
, -
, *
, /
, ==
, !=
, <
, >
, true
, false
等。要求成对重载 (如 ==
和 !=
)。谨慎使用,确保符合直觉。垃圾回收器 (GC) 核心原理
GC.Collect()
(强烈不建议在生产代码中随意调用!)、系统内存不足、AppDomain 卸载、CLR 关闭等。IDisposable
接口与 using
语句 - 管理非托管资源
IDisposable
接口: 定义了 Dispose()
方法,用于释放非托管资源 (也可用于释放托管资源)。Dispose
模式:public class ResourceHolder : IDisposable
{
// 标记是否已释放
private bool _disposed = false;
// 托管资源
private SomeManagedResource _managedResource;
// 非托管资源 (通常用 IntPtr 句柄表示)
private IntPtr _nativeHandle;
// 实现 IDisposable
public void Dispose()
{
Dispose(true); // 释放托管和非托管资源
GC.SuppressFinalize(this); // 告诉GC不需要再调用终结器
}
// 受保护的虚方法,实际释放逻辑
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
// 释放托管资源 (它们也可能实现 IDisposable)
_managedResource?.Dispose();
}
// 释放非托管资源
CloseHandle(_nativeHandle); // 调用 Win32 API 或其他释放方法
_nativeHandle = IntPtr.Zero;
_disposed = true;
}
}
// 终结器 (安全网,防止忘记调用 Dispose)
~ResourceHolder()
{
Dispose(false); // 只释放非托管资源
}
// 其他方法需检查 _disposed 状态,抛出 ObjectDisposedException
}
using
语句: 确保 Dispose()
被调用的语法糖,即使块内发生异常。using (var resource = new ResourceHolder())
{
// 使用 resource
} // 此处自动调用 resource.Dispose()
// C# 8+ 简洁写法 (无需大括号)
using var resource = new ResourceHolder();
// ... 作用域结束时自动 Dispose
IDisposable
? (GC 不管非托管资源)。using
语句的作用和原理? (try
/finally
的包装)。Dispose
模式中 disposing
参数的作用? (区分是 Dispose()
调用还是终结器调用)。GC.SuppressFinalize(this)
的作用? (避免对象进入终结队列,提升性能)。~
) 的作用和局限性? (安全网,但调用时机不确定,不能依赖)。委托 (Delegate) 与事件 (Event)
delegate void MyDelegate(string message);
MyDelegate del = new MyDelegate(MyMethod);
或 MyDelegate del = MyMethod;
(简写)。del("Hello");
或 del?.Invoke("Hello");
(空安全)。+
, +=
) 和移除 (-
, -=
)。调用时按添加顺序依次调用所有方法。返回值是最后一个方法的返回值。=
覆盖)。
public event EventHandler MyEvent;
(EventHandler
是常用预定义委托 delegate void EventHandler(object sender, EventArgs e);
)。obj.MyEvent += HandleEvent;
obj.MyEvent -= HandleEvent;
(重要!避免内存泄漏!)MyEvent?.Invoke(this, EventArgs.Empty);
(空安全)。EventHandler
和 EventArgs
的作用? (标准事件模式)。集合 (Collections)
IEnumerable
/ IEnumerable
: 定义 GetEnumerator()
,支持 foreach
遍历。ICollection
/ ICollection
: 继承 IEnumerable
,添加计数 (Count
)、添加(Add
)、移除(Remove
)、清空(Clear
)等。IList
/ IList
: 继承 ICollection
,添加索引访问 ([index]
)、插入(Insert
)、移除(RemoveAt
)。IDictionary
/ IDictionary
: 存储键值对,按键访问 ([key]
), Keys
, Values
。List
: 动态数组。索引访问快(O(1)
),中间插入/删除慢(O(n)
)。最常用。Dictionary
: 哈希表。按键查找/插入/删除快(接近 O(1)
)。键必须唯一。按键查找首选。HashSet
: 无序唯一值集合。基于哈希。判断存在性(Contains
)、添加(Add
)、集合运算(并、交、差)快。去重、集合运算首选。Queue
: FIFO (先进先出) 队列。Enqueue
, Dequeue
, Peek
。Stack
: LIFO (后进先出) 栈。Push
, Pop
, Peek
。LinkedList
: 双向链表。任意位置插入/删除快(O(1)
),索引访问慢(O(n)
)。AddFirst
, AddLast
, AddBefore
, AddAfter
, Remove
。List
vs Array
? (List
动态扩容)。Dictionary
的工作原理? (哈希表、哈希冲突解决 - 通常链表法)。foreach
遍历时修改集合会怎样? (通常抛出 InvalidOperationException
)。如何安全修改? (遍历时记录要修改的元素,遍历完再处理;或使用 for
循环从后往前删除;或用 ToList()
创建副本遍历)。Dictionary
的键需要做什么? (正确重写 GetHashCode()
和 Equals(object)
/ Equals(T)
)。GetHashCode()
规则: 相等对象必须返回相同哈希码;尽量分布均匀;基于不可变字段计算。Equals
规则: 自反、对称、传递、一致、非空。异常处理 (Exception Handling)
try
-catch
-finally
:
try
: 包含可能抛出异常的代码。catch
: 捕获并处理特定类型或更通用的异常。应优先捕获更具体的异常类型。finally
: 无论是否发生异常都会执行。用于释放资源(即使 catch
块中有 return
或重新抛出异常)。throw
关键字。throw new MyException("message");
/ throw;
(重新抛出当前异常,保留原始堆栈跟踪)。Exception
或其子类 (如 ApplicationException
)。提供有意义的错误信息和上下文。public class MyAppException : Exception
{
public int ErrorCode { get; }
public MyAppException(string message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}
public MyAppException(string message, int errorCode, Exception innerException)
: base(message, innerException)
{
ErrorCode = errorCode;
}
}
TryParse
而非捕获异常)。catch {}
或 catch (Exception ex) { }
不做任何处理/记录)。finally
或 using
中释放资源。try
-catch
包装跨越信任边界(如调用外部服务、读取用户输入)的代码。catch
块时,从最具体到最通用 (Exception
) 排序。finally
块何时执行? (即使有 return
或异常抛出)。throw ex;
和 throw;
的区别? (throw ex;
重置堆栈跟踪为当前点,throw;
保留原始堆栈跟踪)。Exception
? (可能掩盖你无法处理或未预料到的严重错误)。async
/await
(异步编程)
async
: 修饰方法,表示该方法包含异步操作。方法通常返回 Task
, Task
或 ValueTask
/ValueTask
。await
: 用在 async
方法内部,等待一个 Task
(或其他 awaitable
类型) 完成。不会阻塞调用线程! 方法在此处“暂停”,控制权返回给调用者。当 Task
完成时,方法从此处恢复执行(可能在原线程,也可能在另一个线程池线程)。Task
: 表示一个没有返回值的异步操作。Task
: 表示一个返回 TResult
类型值的异步操作。ValueTask
/ ValueTask
: Task
的轻量级替代,适合可能同步完成的操作,避免堆分配开销。void
: 仅用于事件处理程序。调用者无法 await
或捕获异常。尽量避免在其他地方使用。async
方法重写为一个状态机类,管理 await
点的暂停和恢复。async void
(除事件处理器外)。await
返回 Task
的方法 (除非在顶级或 Main
方法中,此时可 .Wait()
/.Result
- 慎用,可能导致死锁!)。ConfigureAwait(false)
:在库代码或无 UI 上下文中,告诉运行时恢复时不强制要求回到原始同步上下文,可提高性能避免死锁。async
方法内部用 try
/catch
。异常会存储在返回的 Task
对象中,在 await
该 Task
时抛出。Task.Run
包装 CPU 密集型同步代码来“伪异步”。真异步基于 I/O 完成端口等机制。async
/await
如何提升性能? (释放线程做其他事,非阻塞)。await
会阻塞线程吗? (不会!)。async void
有什么问题? (调用者无法追踪状态、无法捕获异常)。.Result
/.Wait()
一个需要该上下文恢复的 Task
)。如何避免? (库代码用 ConfigureAwait(false)
;应用层避免同步等待异步方法,坚持 async
全链路)。Task
vs ValueTask
? (何时选用后者?)。var
关键字: 局部变量类型推断。var list = new List();
。编译时确定类型,是语法糖。不影响运行时性能。 在类型明显时增强可读性。nameof
运算符: throw new ArgumentNullException(nameof(param));
。获取变量、类型或成员的字符串名称。重构友好。?.
/ ?[]
: customer?.Address?.City
/ list?[0]
。安全导航,避免 NullReferenceException
。任一环节为 null
则返回 null
。??
/ ??=
: string name = inputName ?? "Default";
/ list ??= new List();
。提供默认值或空时赋值。$
): $"Hello, {name}!"
。比 string.Format
更简洁高效。public string Name { get; set; } = "Unknown";
。public string FullName => $"{FirstName} {LastName}";
/ public void Print() => Console.WriteLine(FullName);
。简化单行方法/属性/索引器/构造/终结器。(string Name, int Age) person = ("Alice", 30);
/ var person = (Name: "Alice", Age: 30);
Console.WriteLine($"{person.Name}, {person.Age}");
(string name, int age) = person;
/ var (name, age) = person;
is
类型模式:if (obj is string s) { ... }
switch
表达式和模式:string result = shape switch
{
Circle c => $"Circle with radius {c.Radius}",
Rectangle r when r.Width == r.Height => $"Square with side {r.Width}",
Rectangle r => $"Rectangle {r.Width}x{r.Height}",
_ => "Unknown shape"
};
public record Person(string FirstName, string LastName);
(简洁定义不可变数据载体)。Equals
/GetHashCode
、ToString
、with
表达式 (非破坏性修改 var newPerson = person with { LastName = "Smith" };
)。Console.WriteLine("Hello World!");
。using
指令 (C# 10.0+): global using System;
在全局文件中声明,整个项目有效。namespace MyNamespace;
(文件内所有代码都在此命名空间)。