100道C#高頻經典面試題帶解析答案
以下是100道C#高頻經典面試題及其詳細解析,涵蓋基礎語法、面向對象編程、集合、異步編程、LINQ等多個方面,旨在幫助初學者和有經驗的開發者全面準備C#相關面試。
🧑 博主簡介:CSDN博客專家、CSDN平臺優質創作者,高級開發工程師,數學專業,10年以上C/C++, C#, Java等多種編程語言開發經驗,擁有高級工程師證書;擅長C/C++、C#等開發語言,熟悉Java常用開發技術,能熟練應用常用數據庫SQL server,Oracle,mysql,postgresql等進行開發應用,熟悉DICOM醫學影像及DICOM協議,業余時間自學JavaScript,Vue,qt,python等,具備多種混合語言開發能力。撰寫博客分享知識,致力于幫助編程愛好者共同進步。歡迎關注、交流及合作,提供技術支持與解決方案。
技術合作請加本人wx(注明來自csdn):xt20160813
基礎語法
1. 什么是C#,它的主要特點是什么?
答案:
C#(發音為C Sharp)是一種由微軟開發的現代、通用、面向對象的編程語言,作為.NET框架的一部分,主要用于開發Windows應用、Web應用、移動應用等。其主要特點包括:
- 面向對象:支持類、對象、繼承、多態、封裝等面向對象的特性。
- 類型安全:強類型語言,提供類型檢查,減少運行時錯誤。
- 豐富的類庫:提供大量的標準庫,簡化開發。
- 跨平臺:通過.NET Core和.NET 5/6等實現跨平臺開發。
- 異步編程:內置支持異步編程模型(async/await),提高應用性能。
- 垃圾回收:自動內存管理,減少內存泄漏和錯誤。
2. C#中struct
和class
的區別是什么?
答案:
-
類型:
struct
是值類型,存儲在棧上。class
是引用類型,存儲在堆上。
-
繼承:
struct
不支持繼承(除了實現接口)。class
支持繼承,可以有基類和派生類。
-
默認構造函數:
struct
有隱式的無參構造函數,不能顯式定義。class
可以定義顯式的無參構造函數。
-
用途:
struct
適用于小型、不可變的數據結構,如點、顏色等。class
適用于需要復雜行為和狀態管理的對象。
3. 什么是屬性(Property)?與字段(Field)相比有什么區別?
答案:
- **字段(Field)**是類中用于存儲數據的變量,通常聲明為私有(private)。
- **屬性(Property)**是一種類成員,通過get和set訪問器控制對字段的訪問,提供數據的封裝和驗證。
區別:
- 封裝性:屬性可以控制讀寫權限,添加邏輯驗證,而字段直接暴露數據,缺乏封裝。
- 數據隱藏:通過屬性隱藏內部字段,實現更靈活的接口設計。
- 兼容性:屬性可以在不改變外部接口的情況下修改內部實現。
示例:
private int _age;public int Age
{get { return _age; }set { if (value >= 0)_age = value; }
}
4. C#中的readonly
和const
有什么區別?
答案:
-
const
:- 在編譯時確定值。
- 必須初始化,且只能使用字面量。
- 默認靜態(static)的。
- 只能用于基本類型和字符串。
-
readonly
:- 在運行時確定值。
- 可以在聲明時或構造函數中初始化。
- 不是默認靜態的,除非顯式聲明為
static readonly
。 - 可以用于復雜類型。
示例:
public const double PI = 3.14159;
public readonly DateTime creationTime;public MyClass()
{creationTime = DateTime.Now;
}
5. 什么是委托(Delegate)?與事件(Event)有什么關系?
答案:
-
**委托(Delegate)**是C#中一種類型安全的函數指針,允許將方法作為參數傳遞或賦值給變量。
示例:
public delegate void Notify(string message);public void ShowMessage(string msg) {Console.WriteLine(msg); }Notify notifier = ShowMessage; notifier("Hello, World!");
-
**事件(Event)**是基于委托的一種機制,用于在對象之間傳遞通知。事件通常用于發布-訂閱模式,允許多個訂閱者響應某個動作。
關系:事件使用委托作為其底層類型,定義了事件處理的方法簽名。
示例:
public event Notify OnNotify;public void TriggerEvent(string msg) {OnNotify?.Invoke(msg); }
6. 什么是interface
,它與抽象類有什么區別?
答案:
-
interface
:- 定義一組方法和屬性的簽名,不包含任何實現。
- 類或結構體可以實現多個接口,實現接口中的所有成員。
- 不支持字段和構造函數。
-
抽象類:
- 可以包含抽象方法(無實現)和具體方法(有實現)。
- 只能單繼承,一個類只能繼承一個抽象類。
- 可以包含字段和構造函數。
區別:
- 實現:接口僅定義契約,不提供實現;抽象類可以部分實現。
- 繼承:類可以實現多個接口,但只能繼承一個抽象類。
- 成員:接口不能包含字段,抽象類可以。
7. C#中的enum
如何使用?有什么限制?
答案:
-
使用:
- 定義一組命名的整型常量,用于提高代碼的可讀性和維護性。
示例:
public enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };Days today = Days.Monday;
-
限制:
- 基礎類型默認為
int
,但可以指定其他整數類型(byte, sbyte, short, ushort, int, uint, long, ulong)。 - 枚舉成員的名稱必須唯一,不能重復。
- 不能包含成員方法,只能包含成員字段。
- 基礎類型默認為
8. 什么是partial
類,它的作用是什么?
答案:
-
partial
類:允許將一個類、結構體或接口的定義分布在多個文件中。編譯器會將所有部分組合成一個完整的類型。 -
作用:
- 便于多人協作開發,分工編寫不同部分。
- 支持代碼生成工具生成部分代碼,開發者編寫手動部分。
- 提高代碼的組織性和可維護性。
示例:
文件1:
public partial class MyClass
{public void MethodA() { /* ... */ }
}
文件2:
public partial class MyClass
{public void MethodB() { /* ... */ }
}
9. 什么是索引器(Indexer)?如何定義和使用?
答案:
-
**索引器(Indexer)**允許對象像數組一樣通過索引訪問其內部數據。它使用
this
關鍵字,并帶有參數列表。 -
定義:
public class SampleCollection {private int[] arr = new int[100];public int this[int i]{get { return arr[i]; }set { arr[i] = value; }} }
-
使用:
SampleCollection collection = new SampleCollection(); collection[0] = 42; int value = collection[0];
10. 什么是擴展方法(Extension Method)?如何定義和使用?
答案:
-
**擴展方法(Extension Method)**允許向現有的類型添加新方法,而無需修改該類型的源代碼或創建派生類型。
-
定義:
- 必須在靜態靜態類中定義靜態方法,第一個參數使用
this
關鍵字表示要擴展的類型。
示例:
public static class StringExtensions {public static bool IsNullOrEmpty(this string str){return string.IsNullOrEmpty(str);} }
- 必須在靜態靜態類中定義靜態方法,第一個參數使用
-
使用:
string s = ""; bool result = s.IsNullOrEmpty(); // 調用擴展方法
面向對象編程
11. 什么是封裝(Encapsulation)?
答案:
封裝是面向對象編程的基本特性之一,通過將數據(字段)和操作數據的方法(函數)綁定在一起,并隱藏內部實現細節,只暴露必要的接口,以提高代碼的模塊性和安全性。封裝通過訪問修飾符(如private
, public
, protected
)實現數據隱藏,防止外部直接訪問和修改對象的內部狀態。
示例:
public class Person
{private string name; // 隱藏字段public string Name // 公開屬性{get { return name; }set { name = value; }}public void Display(){Console.WriteLine($"Name: {name}");}
}
12. 什么是繼承(Inheritance)?C#如何實現繼承?
答案:
-
**繼承(Inheritance)**是面向對象編程的關鍵特性,允許一個類(子類)繼承另一個類(基類)的屬性和方法,從而實現代碼復用和層次化設計。
-
C#實現:
- 使用冒號(:)符號,子類繼承基類。
示例:
public class Animal {public void Eat() { Console.WriteLine("Eating"); } }public class Dog : Animal {public void Bark() { Console.WriteLine("Barking"); } }
使用:
Dog dog = new Dog(); dog.Eat(); // 繼承自Animal dog.Bark(); // Dog自身的方法
13. 什么是多態(Polymorphism)?它有哪些形式?
答案:
-
**多態(Polymorphism)**指對象在不同情境下表現出的多種形態,是面向對象編程的核心特性之一。多態使得同一個接口或方法可以有不同的實現。
-
主要形式:
-
編譯時多態(Static Polymorphism):
- 通過方法重載(Overloading)和運算符重載(Operator Overloading)實現。
-
運行時多態(Dynamic Polymorphism):
- 通過方法重寫(Overriding)和接口實現,實現基類引用指向派生類對象時,調用派生類的實現。
-
示例:
public class Animal
{public virtual void Speak(){Console.WriteLine("Animal speaks");}
}public class Cat : Animal
{public override void Speak(){Console.WriteLine("Meow");}
}public class Dog : Animal
{public override void Speak(){Console.WriteLine("Woof");}
}// 使用多態
Animal myAnimal = new Dog();
myAnimal.Speak(); // 輸出: WoofmyAnimal = new Cat();
myAnimal.Speak(); // 輸出: Meow
14. 什么是抽象類(Abstract Class)?如何使用?
答案:
-
**抽象類(Abstract Class)**是一種不能被實例化的類,用于提供基類的一部分實現,并定義一些必須由派生類實現的抽象方法。抽象類通過
abstract
關鍵字聲明。 -
使用場景:
- 當需要定義一個通用的基類,并希望派生類實現具體的行為時。
- 提供共享代碼和接口。
-
定義和使用:
public abstract class Shape {public abstract double Area(); // 抽象方法public void Display(){Console.WriteLine($"The area is {Area()}");} }public class Circle : Shape {public double Radius { get; set; }public Circle(double radius){Radius = radius;}public override double Area(){return Math.PI * Radius * Radius;} }// 使用 Shape myCircle = new Circle(5); myCircle.Display(); // 輸出: The area is 78.53981633974483
15. C#中的sealed
關鍵字有什么作用?
答案:
-
sealed
關鍵字用于類或類成員,限制其進一步繼承或重寫。 -
作用:
-
封閉類繼承:當用于類時,表示該類不能被繼承。
示例:
public sealed class FinalClass { }// 下面的代碼將導致編譯錯誤 public class DerivedClass : FinalClass { }
-
封閉方法重寫:當用于虛方法時,表示該方法不能被派生類重寫。
示例:
public class BaseClass {public virtual void Display() { } }public class DerivedClass : BaseClass {public sealed override void Display() { } }// 下面的代碼將導致編譯錯誤 public class FurtherDerivedClass : DerivedClass {public override void Display() { } }
-
16. 什么是接口繼承?如何實現多接口繼承?
答案:
-
**接口繼承(Interface Inheritance)**指一個接口可以繼承一個或多個其他接口,繼承后的接口包含所有基接口的方法和屬性。
-
多接口繼承:C#中,類或結構體可以實現多個接口,通過逗號分隔接口名稱。
-
示例:
public interface IPrintable {void Print(); }public interface IScannable {void Scan(); }public class MultiFunctionPrinter : IPrintable, IScannable {public void Print(){Console.WriteLine("Printing...");}public void Scan(){Console.WriteLine("Scanning...");} }
17. 什么是靜態構造函數(Static Constructor)?
答案:
-
**靜態構造函數(Static Constructor)**是用于初始化類的靜態成員的特殊構造函數。它在類第一次被訪問之前自動調用,且僅調用一次。
-
特點:
- 無訪問修飾符,不能有參數。
- 不能直接調用。
- 適用于初始化靜態字段或執行一次性的類級別初始化操作。
-
示例:
public class Configuration {public static readonly string AppName;static Configuration(){AppName = "MyApplication";// 其他初始化操作} }// 使用 Console.WriteLine(Configuration.AppName); // 輸出: MyApplication
18. 什么是泛型(Generics)?它的優勢是什么?
答案:
-
**泛型(Generics)**允許在類、結構體、接口、方法等的定義中使用類型參數,使代碼更具重用性和類型安全性。
-
優勢:
- 類型安全:在編譯時檢查類型,減少運行時錯誤。
- 性能:避免裝箱和拆箱,提高性能,尤其在集合操作中。
- 代碼復用:編寫一次泛型代碼,適用于多種數據類型。
-
示例:
public class GenericList<T> {private T[] items = new T[100];private int index = 0;public void Add(T item){items[index++] = item;}public T Get(int i){return items[i];} }// 使用 GenericList<int> intList = new GenericList<int>(); intList.Add(1); Console.WriteLine(intList.Get(0)); // 輸出: 1GenericList<string> stringList = new GenericList<string>(); stringList.Add("Hello"); Console.WriteLine(stringList.Get(0)); // 輸出: Hello
19. 什么是命名空間(Namespace)?它的作用是什么?
答案:
-
**命名空間(Namespace)**是用于組織代碼的邏輯容器,防止命名沖突,提升代碼的可讀性和維護性。
-
作用:
- 組織代碼:將相關類、接口、結構體等分組,便于管理。
- 避免命名沖突:不同命名空間中的同名類型互不影響。
- 便于維護:清晰的結構使代碼更易于維護和理解。
-
示例:
namespace Company.Project.Module {public class MyClass{// ...} }// 使用 using Company.Project.Module;MyClass obj = new MyClass();
20. 什么是using
語句,有什么作用?
答案:
using
語句有兩種主要用途:-
命名空間導入:通過
using
關鍵字引入命名空間,使得在代碼中可以直接使用該命名空間下的類型,而無需全名限定。示例:
using System.Text;StringBuilder sb = new StringBuilder();
-
資源管理:用于確保實現了
IDisposable
接口的對象在使用完畢后被正確釋放,自動調用Dispose
方法,避免資源泄漏。示例:
using (FileStream fs = new FileStream("file.txt", FileMode.Open)) {// 使用文件流 } // 自動調用 fs.Dispose()
-
集合與泛型
21. C#中有哪些常用的集合類型?
答案:
C#中常用的集合類型主要分為兩類:非泛型集合和泛型集合。
-
非泛型集合(位于
System.Collections
命名空間):ArrayList
Hashtable
Queue
Stack
SortedList
-
泛型集合(位于
System.Collections.Generic
命名空間):List<T>
Dictionary<TKey, TValue>
Queue<T>
Stack<T>
LinkedList<T>
HashSet<T>
SortedDictionary<TKey, TValue>
SortedSet<T>
此外,還有并發集合(System.Collections.Concurrent
)和只讀集合(System.Collections.ObjectModel
)等。
22. 什么是List<T>
,它的特點是什么?
答案:
-
**
List<T>
**是一個泛型動態數組,位于System.Collections.Generic
命名空間,用于存儲同一類型的對象,提供了動態擴展、索引訪問等功能。 -
特點:
- 動態大小:可以根據需要自動調整大小。
- 泛型:類型安全,避免運行時類型錯誤。
- 豐富的方法:提供諸如
Add
,Remove
,Find
,Sort
等多種方法,簡化操作。 - 索引訪問:支持通過索引快速訪問元素。
- 性能:底層使用數組,提供較高的訪問性能。
-
示例:
List<int> numbers = new List<int>(); numbers.Add(1); numbers.Add(2); numbers.Add(3);Console.WriteLine(numbers[1]); // 輸出: 2numbers.Remove(2); Console.WriteLine(numbers.Count); // 輸出: 2
23. 什么是Dictionary<TKey, TValue>
,它有哪些常用方法?
答案:
-
**
Dictionary<TKey, TValue>
**是一個基于哈希表實現的泛型集合,存儲鍵值對,提供快速的查找、插入和刪除操作。 -
特點:
- 鍵唯一:每個鍵必須唯一,但不同鍵可以對應相同的值。
- 快速查找:通過鍵進行高效查找。
- 泛型:類型安全,鍵和值可以是任何類型。
-
常用方法:
Add(TKey key, TValue value)
:添加鍵值對。Remove(TKey key)
:根據鍵移除鍵值對。TryGetValue(TKey key, out TValue value)
:嘗試獲取指定鍵的值。ContainsKey(TKey key)
:檢查是否包含指定鍵。Clear()
:清空字典。Keys
和Values
屬性:獲取所有鍵和值的集合。
-
示例:
Dictionary<string, int> ages = new Dictionary<string, int>(); ages.Add("Alice", 30); ages.Add("Bob", 25);if (ages.TryGetValue("Alice", out int age)) {Console.WriteLine($"Alice's age is {age}"); }foreach (var key in ages.Keys) {Console.WriteLine(key); }
24. 什么是HashSet<T>
,它的用途是什么?
答案:
-
**
HashSet<T>
**是一個泛型集合,存儲唯一的元素,基于哈希表實現,主要用于集合操作如并集、交集和差集。 -
特點:
- 元素唯一:自動去重,確保集合中沒有重復元素。
- 高效的查找:提供快速的添加、刪除和查找操作。
- 不保持順序:元素的存儲順序不固定。
-
用途:
- 去重:從數據集中快速移除重復項。
- 集合操作:執行并集、交集、差集等操作。
- 高效查找:在需要頻繁查找操作的場景中使用。
-
示例:
HashSet<int> numbers = new HashSet<int>(); numbers.Add(1); numbers.Add(2); numbers.Add(2); // 重復元素,添加失敗Console.WriteLine(numbers.Count); // 輸出: 2HashSet<int> otherNumbers = new HashSet<int> { 2, 3, 4 }; numbers.IntersectWith(otherNumbers);foreach (var num in numbers) {Console.WriteLine(num); // 輸出: 2 }
25. C#中如何遍歷Dictionary
集合?
答案:
可以通過foreach
循環遍歷Dictionary<TKey, TValue>
,訪問每個鍵值對。常見的遍歷方式包括:
-
遍歷鍵值對:
Dictionary<string, int> ages = new Dictionary<string, int> {{ "Alice", 30 },{ "Bob", 25 } };foreach (KeyValuePair<string, int> kvp in ages) {Console.WriteLine($"Name: {kvp.Key}, Age: {kvp.Value}"); }
-
遍歷鍵:
foreach (string key in ages.Keys) {Console.WriteLine($"Name: {key}"); }
-
遍歷值:
foreach (int value in ages.Values) {Console.WriteLine($"Age: {value}"); }
26. 什么是協變(Covariance)和逆變(Contravariance)?
答案:
-
**協變(Covariance)和逆變(Contravariance)**是泛型類型參數中的類型轉換規則,用于實現更靈活的類型系統。
-
協變:
- 允許將派生類型的泛型接口賦值給基類型的泛型接口。
- 適用于輸出方向(返回類型)。
- 使用
out
關鍵字。
示例:
IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings; // 協變
-
逆變:
- 允許將基類型的泛型接口賦值給派生類型的泛型接口。
- 適用于輸入方向(參數類型)。
- 使用
in
關鍵字。
示例:
Action<object> actObject = obj => Console.WriteLine(obj); Action<string> actString = actObject; // 逆變
-
應用:
- 提高泛型代碼的靈活性和可重用性。
- 常用于委托和接口,如
IEnumerable<out T>
、IComparer<in T>
。
27. C#中的Queue<T>
和Stack<T>
有什么區別?
答案:
-
**
Queue<T>
**是先進先出(FIFO)的數據結構,適用于需要按順序處理元素的場景。主要操作:
Enqueue(T item)
:添加元素到隊列尾部。Dequeue()
:移除并返回隊列頭部元素。Peek()
:返回隊列頭部元素,但不移除。
-
**
Stack<T>
**是后進先出(LIFO)的數據結構,適用于需要逆序處理元素的場景。主要操作:
Push(T item)
:添加元素到棧頂。Pop()
:移除并返回棧頂元素。Peek()
:返回棧頂元素,但不移除。
-
示例:
Queue<int> queue = new Queue<int>(); queue.Enqueue(1); queue.Enqueue(2); Console.WriteLine(queue.Dequeue()); // 輸出: 1Stack<int> stack = new Stack<int>(); stack.Push(1); stack.Push(2); Console.WriteLine(stack.Pop()); // 輸出: 2
28. 什么是LinkedList<T>
,它的優勢和劣勢是什么?
答案:
-
**
LinkedList<T>
**是一個基于雙向鏈表實現的泛型集合,支持快速的插入和刪除操作。 -
優勢:
- 快速插入和刪除:在任何位置插入或刪除元素的時間復雜度為O(1),只需調整指針。
- 動態大小:無需預先分配固定大小。
-
劣勢:
- 內存消耗高:每個節點需要額外存儲前后指針。
- 訪問速度慢:無法通過索引快速訪問元素,需從頭或尾遍歷。
- 緩存局部性差:由于元素分散存儲,可能導致緩存命中率低。
-
適用場景:
- 需要頻繁在中間位置插入或刪除元素的場景。
- 不需要隨機訪問元素的場景。
-
示例:
LinkedList<string> linkedList = new LinkedList<string>(); linkedList.AddLast("First"); linkedList.AddLast("Second"); linkedList.AddFirst("Zero");foreach (var item in linkedList) {Console.WriteLine(item); } // 輸出: // Zero // First // Second
29. 什么是只讀集合(ReadOnly Collection)?如何創建?
答案:
-
**只讀集合(ReadOnly Collection)**是無法修改的集合視圖,提供對基礎集合的只讀訪問。
-
創建方式:
- 使用
ReadOnlyCollection<T>
包裝一個現有的IList<T>
。
- 使用
-
用途:
- 提供安全的只讀訪問,防止外部代碼修改集合。
- 保護內部數據結構,增強封裝性。
-
示例:
using System.Collections.ObjectModel;List<int> list = new List<int> { 1, 2, 3 }; ReadOnlyCollection<int> readOnly = new ReadOnlyCollection<int>(list);Console.WriteLine(readOnly[0]); // 輸出: 1 // readOnly.Add(4); // 編譯錯誤,無法修改
30. 什么是ArrayList
,為什么更推薦使用泛型集合如List<T>
?
答案:
-
**
ArrayList
**是非泛型的動態數組集合,屬于System.Collections
命名空間,能夠存儲任何類型的對象。 -
原因不推薦使用
ArrayList
:- 類型不安全:由于存儲為
object
,需要進行裝箱和拆箱操作,容易引發運行時錯誤。 - 性能低下:頻繁的裝箱和拆箱導致性能下降,尤其在處理值類型時。
- 缺乏泛型優勢:無法利用泛型帶來的類型檢查和性能優化。
- 類型不安全:由于存儲為
-
推薦使用:
- 使用**
List<T>
**,提供類型安全、無需裝箱、性能更高、支持泛型特性。
- 使用**
-
示例:
ArrayList arrayList = new ArrayList(); arrayList.Add(1); arrayList.Add("Two");// 需要進行類型檢查或轉換 foreach (var item in arrayList) {if (item is int)Console.WriteLine((int)item);else if (item is string)Console.WriteLine((string)item); }// 使用泛型集合 List<int> list = new List<int> { 1, 2, 3 }; foreach (int num in list) {Console.WriteLine(num); // 無需轉換,類型安全 }
異常處理
31. C#中如何實現異常處理?常用的關鍵字有哪些?
答案:
-
異常處理通過
try-catch-finally
語句塊實現,捕獲和處理運行時錯誤,保證程序的穩定性。 -
關鍵字:
try
:包裹可能引發異常的代碼塊。catch
:捕獲特定類型的異常并進行處理。finally
:無論是否發生異常,都會執行的代碼塊,通常用于資源釋放。throw
:引發異常或重新拋出捕獲的異常。
-
示例:
try {int[] numbers = {1, 2, 3};Console.WriteLine(numbers[5]); // 可能引發IndexOutOfRangeException } catch (IndexOutOfRangeException ex) {Console.WriteLine("索引越界錯誤: " + ex.Message); } catch (Exception ex) {Console.WriteLine("其他錯誤: " + ex.Message); } finally {Console.WriteLine("無論是否發生異常,都會執行。"); }
32. 如何自定義異常類?
答案:
-
步驟:
- 創建一個繼承自
Exception
或其派生類的類。 - 實現至少一個構造函數,通常包括無參構造函數、帶消息參數的構造函數,以及支持序列化的構造函數。
- 創建一個繼承自
-
示例:
[Serializable] public class MyCustomException : Exception {public MyCustomException() { }public MyCustomException(string message) : base(message) { }public MyCustomException(string message, Exception inner) : base(message, inner) { }protected MyCustomException(SerializationInfo info, StreamingContext context) : base(info, context) { } }// 使用自定義異常 public void DoSomething(int value) {if (value < 0)throw new MyCustomException("值不能為負數"); }
33. 什么是finally
塊,它的作用是什么?
答案:
-
finally
塊是try-catch
結構的一部分,用于包含無論是否發生異常都需要執行的代碼,如資源釋放、清理操作等。 -
作用:
- 確保資源被正確釋放,避免資源泄漏。
- 執行必要的清理操作,無論異常是否被捕獲。
-
特點:
finally
塊是可選的,可以單獨與try
配合使用。- 如果有
return
語句,finally
塊仍會執行。
-
示例:
try {// 可能引發異常的代碼 } catch (Exception ex) {// 異常處理 } finally {// 清理操作Console.WriteLine("執行 finally 塊"); }
34. 什么是throw
和throw ex
的區別?
答案:
-
throw
:- 重新拋出當前捕獲的異常,保留原始的堆棧跟蹤信息。
-
throw ex
:- 拋出一個新的異常實例,導致原始的堆棧跟蹤信息丟失,顯示異常發生的位置為
throw ex
處。
- 拋出一個新的異常實例,導致原始的堆棧跟蹤信息丟失,顯示異常發生的位置為
-
區別:
- 使用
throw
能夠保留原始異常的上下文,便于調試和錯誤追蹤。 - 使用
throw ex
會丟失原始的堆棧跟蹤,降低異常信息的可用性。
- 使用
-
示例:
try {// 可能引發異常 } catch (Exception ex) {throw; // 保留堆棧信息// throw ex; // 丟失原始堆棧信息 }
35. 什么是自定義異常消息,如何實現?
答案:
-
自定義異常消息是指在拋出異常時,提供一個自定義的錯誤信息,以便更清晰地描述異常的原因。
-
實現方法:
- 在拋出異常時,通過構造函數傳遞自定義的錯誤消息。
- 自定義異常類中可以添加額外的屬性或方法,以提供更詳細的信息。
-
示例:
public class InvalidAgeException : Exception {public InvalidAgeException(string message) : base(message) { } }public void SetAge(int age) {if (age < 0 || age > 120)throw new InvalidAgeException("年齡必須在0到120之間。");// 設置年齡 }// 使用 try {SetAge(-5); } catch (InvalidAgeException ex) {Console.WriteLine(ex.Message); // 輸出: 年齡必須在0到120之間。 }
異步編程
36. C#中如何實現異步編程?關鍵字有哪些?
答案:
-
異步編程允許程序在執行耗時操作時不阻塞主線程,提高應用響應性和性能。
-
關鍵字:
async
:標記方法為異步方法,允許使用await
關鍵字。await
:暫停異步方法的執行,直到等待的任務完成,然后繼續執行。Task
和Task<T>
:表示異步操作的結果和狀態。async void
:用于事件處理器,不推薦在其他場景使用,因無法捕獲異常。
-
示例:
public async Task<int> GetDataAsync() {// 模擬異步操作await Task.Delay(1000);return 42; }public async void DisplayData() {int result = await GetDataAsync();Console.WriteLine(result); }// 使用 DisplayData(); // 輸出: 42 (延遲1秒)
37. 什么是Task
和Task<T>
?
答案:
-
Task
:- 表示一個異步操作,不返回任何結果。
- 用于表示執行中的操作。
-
Task<T>
:- 表示一個異步操作,返回一個類型為
T
的結果。 - 允許在異步操作完成后獲取結果。
- 表示一個異步操作,返回一個類型為
-
區別:
Task
用于無返回值的異步方法。Task<T>
用于有返回值的異步方法。
-
示例:
// 使用 Task public async Task SaveDataAsync(string data) {await Task.Delay(1000); // 模擬保存數據Console.WriteLine("數據已保存"); }// 使用 Task<T> public async Task<int> ComputeValueAsync() {await Task.Delay(1000); // 模擬計算return 100; }// 調用 await SaveDataAsync("Sample Data"); int value = await ComputeValueAsync(); Console.WriteLine(value); // 輸出: 100
38. 什么是async
和await
關鍵字?
答案:
-
async
:- 標記方法為異步方法,允許使用
await
關鍵字。 - 方法返回類型通常為
Task
、Task<T>
或void
。
- 標記方法為異步方法,允許使用
-
await
:- 用于等待一個異步操作完成,非阻塞地暫停方法執行,待等待的任務完成后繼續執行。
- 只能在標記為
async
的方法中使用。
-
工作原理:
- 當
await
等待的任務未完成時,方法會掛起,釋放當前線程。 - 任務完成后,方法會恢復執行,繼續后續代碼。
- 當
-
示例:
public async Task<string> FetchDataAsync() {using (HttpClient client = new HttpClient()){string result = await client.GetStringAsync("https://example.com");return result;} }public async void DisplayData() {string data = await FetchDataAsync();Console.WriteLine(data); }
39. 如何處理異步方法中的異常?
答案:
-
使用
try-catch
塊:在異步方法中,可以使用try-catch
結構捕獲并處理異常。示例:
public async Task<string> GetDataAsync() {try{// 可能引發異常的異步操作return await File.ReadAllTextAsync("nonexistentfile.txt");}catch (FileNotFoundException ex){Console.WriteLine("文件未找到: " + ex.Message);return null;} }
-
捕獲任務異常:
- 當使用
Task
時,可以通過await
拋出任務的異常,隨后被try-catch
捕獲。 - 如果直接使用
Task
,可以通過訪問Task.Exception
屬性獲取異常信息。
- 當使用
-
注意事項:
- 避免使用
async void
,因其異常無法被外部捕獲。 - 在異步事件處理器中,異常需要在內部處理,以防止程序崩潰。
- 避免使用
40. 什么是ConfigureAwait(false)
,它的作用是什么?
答案:
-
**
ConfigureAwait(false)
**用于告訴編譯器在等待異步操作完成后,不需要恢復到原來的同步上下文(如UI線程)。 -
作用:
- 提高應用性能,減少上下文切換的開銷。
- 避免死鎖問題,尤其在庫代碼和服務端應用中。
-
使用場景:
- 在非UI線程、后臺處理任務或庫代碼中使用異步方法。
-
示例:
public async Task<string> GetDataAsync() {using (HttpClient client = new HttpClient()){// 異步調用,但不需要恢復到原同步上下文string result = await client.GetStringAsync("https://example.com").ConfigureAwait(false);return result;} }
LINQ(Language Integrated Query)
41. 什么是LINQ,它的主要用途是什么?
答案:
-
**LINQ(Language Integrated Query)**是C#中的一組語言特性,允許開發者在語言層面上對數據進行查詢、過濾、排序和轉換等操作,無需使用外部查詢語言(如SQL)。
-
主要用途:
- 提高代碼的可讀性和簡潔性。
- 在各種數據源(集合、數據庫、XML等)上執行統一的查詢操作。
- 利用強類型和編譯時檢查,減少運行時錯誤。
-
示例:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; var evenNumbers = from num in numberswhere num % 2 == 0select num;foreach (var n in evenNumbers) {Console.WriteLine(n); // 輸出: 2, 4 }
42. 什么是延遲執行(Deferred Execution)?
答案:
-
**延遲執行(Deferred Execution)**指LINQ查詢在創建時并不立即執行,而是當查詢的結果被實際迭代(如使用
foreach
)或調用終結操作(如ToList
)時才執行。 -
作用:
- 提高性能,避免不必要的計算。
- 支持動態查詢,查詢操作可以隨數據源的變化而變化。
-
示例:
List<int> numbers = new List<int> { 1, 2, 3 };var query = numbers.Where(n => n > 1); // 查詢未執行numbers.Add(4);foreach (var n in query) {Console.WriteLine(n); // 輸出: 2, 3, 4 }
43. IEnumerable<T>
和IQueryable<T>
有什么區別?
答案:
-
IEnumerable<T>
:- 定義在
System.Collections.Generic
命名空間。 - 支持對內存中對象集合的迭代,適用于LINQ to Objects。
- 查詢在本地執行,返回結果集。
- 定義在
-
IQueryable<T>
:- 定義在
System.Linq
命名空間。 - 繼承自
IEnumerable<T>
,支持LINQ到其他數據源(如數據庫、XML)。 - 查詢表達式可以被轉換為數據源特定的查詢,如SQL,優化查詢性能。
- 定義在
-
區別:
IEnumerable<T>
適合操作內存中的集合,IQueryable<T>
適合延伸到外部數據源。IQueryable<T>
支持表達式樹,允許在數據源端進行查詢優化。
44. 什么是LINQ的標準查詢運算符?
答案:
LINQ的標準查詢運算符是指一系列擴展方法,用于對數據源進行查詢操作。這些運算符分為幾類:
-
過濾:
Where
:篩選符合條件的元素。OfType
:篩選特定類型的元素。Distinct
:移除重復元素。
-
排序:
OrderBy
、OrderByDescending
:按升序或降序排序。ThenBy
、ThenByDescending
:在已有排序的基礎上進行次級排序。
-
分組:
GroupBy
:將元素按鍵值分組。
-
投影:
Select
:選擇或轉換元素。SelectMany
:將多維數據展開為一維。
-
集合操作:
Join
:連接兩個數據源。GroupJoin
:進行分組連接。Union
、Intersect
、Except
:集合的并、交、差操作。
-
量化:
All
、Any
:檢查所有或任意元素是否滿足條件。Count
、LongCount
:計算元素數量。Min
、Max
、Sum
、Average
:計算最小值、最大值、總和、平均值。
-
元素操作:
First
、FirstOrDefault
:獲取第一個元素或默認值。Last
、LastOrDefault
:獲取最后一個元素或默認值。Single
、SingleOrDefault
:獲取唯一的元素或默認值。
-
轉換:
ToList
、ToArray
:將查詢結果轉換為列表或數組。ToDictionary
:將查詢結果轉換為字典。
-
生成:
Empty
:返回一個空的序列。
45. 如何使用LINQ實現分組操作?
答案:
使用GroupBy
運算符可以將元素按指定的鍵進行分組,返回一個分組的集合,每個分組中包含鍵和相關聯的元素。
示例:
public class Student
{public string Name { get; set; }public string Grade { get; set; }
}List<Student> students = new List<Student>
{new Student { Name = "Alice", Grade = "A" },new Student { Name = "Bob", Grade = "B" },new Student { Name = "Charlie", Grade = "A" },new Student { Name = "David", Grade = "C" }
};// 按成績分組
var grouped = students.GroupBy(s => s.Grade);foreach (var group in grouped)
{Console.WriteLine($"Grade: {group.Key}");foreach (var student in group){Console.WriteLine($" - {student.Name}");}
}// 輸出:
// Grade: A
// - Alice
// - Charlie
// Grade: B
// - Bob
// Grade: C
// - David
46. 什么是LINQ的延遲執行和立即執行?
答案:
-
延遲執行(Deferred Execution):
- LINQ查詢在定義時不立即執行,直到其結果被迭代或調用終結運算符(如
ToList
,ToArray
)時才執行。 - 有利于性能優化和動態查詢。
- LINQ查詢在定義時不立即執行,直到其結果被迭代或調用終結運算符(如
-
立即執行(Immediate Execution):
- LINQ查詢在定義時立即執行,獲取結果并存儲。
- 常用的終結運算符觸發立即執行,如
ToList
,ToArray
,Count
,First
等。
-
示例:
List<int> numbers = new List<int> { 1, 2, 3 };// 延遲執行 var query = numbers.Where(n => n > 1); // 查詢未執行numbers.Add(4);foreach (var num in query) {Console.WriteLine(num); // 輸出: 2, 3, 4 }// 立即執行 var list = numbers.Where(n => n > 1).ToList(); // 查詢立即執行并存儲結果numbers.Add(5);foreach (var num in list) {Console.WriteLine(num); // 輸出: 2, 3, 4 }
47. 如何在LINQ中使用匿名類型?
答案:
-
**匿名類型(Anonymous Types)**允許在LINQ查詢中創建臨時類型,包含一組只讀屬性,無需預先定義類。
-
使用場景:
- 當查詢結果需要特定的項目集合,但不想創建新的類時。
- 提高查詢的靈活性和簡潔性。
-
示例:
var products = new List<Product> {new Product { Name = "Apple", Category = "Fruit", Price = 1.2 },new Product { Name = "Carrot", Category = "Vegetable", Price = 0.8 },new Product { Name = "Banana", Category = "Fruit", Price = 1.1 } };var fruitProducts = from p in productswhere p.Category == "Fruit"select new { p.Name, p.Price };foreach (var item in fruitProducts) {Console.WriteLine($"Name: {item.Name}, Price: {item.Price}"); }// 輸出: // Name: Apple, Price: 1.2 // Name: Banana, Price: 1.1
48. 如何使用LINQ進行連接操作?
答案:
使用Join
運算符可以在兩個序列之間根據某個鍵進行內連接(Inner Join),返回匹配的元素對。
示例:
public class Student
{public int ID { get; set; }public string Name { get; set; }
}public class Score
{public int StudentID { get; set; }public int ScoreValue { get; set; }
}List<Student> students = new List<Student>
{new Student { ID = 1, Name = "Alice" },new Student { ID = 2, Name = "Bob" },new Student { ID = 3, Name = "Charlie" }
};List<Score> scores = new List<Score>
{new Score { StudentID = 1, ScoreValue = 85 },new Score { StudentID = 2, ScoreValue = 90 },new Score { StudentID = 1, ScoreValue = 88 }
};// 內連接
var studentScores = from s in studentsjoin sc in scores on s.ID equals sc.StudentIDselect new { s.Name, sc.ScoreValue };foreach (var item in studentScores)
{Console.WriteLine($"Name: {item.Name}, Score: {item.ScoreValue}");
}// 輸出:
// Name: Alice, Score: 85
// Name: Alice, Score: 88
// Name: Bob, Score: 90
49. 什么是LINQ的方法語法和查詢語法?有什么區別?
答案:
-
查詢語法(Query Syntax):
- 類似SQL的語法,將查詢表達為
from
,where
,select
等關鍵字的組合。 - 更具可讀性,適合熟悉SQL的開發者。
- 類似SQL的語法,將查詢表達為
-
方法語法(Method Syntax):
- 基于擴展方法鏈式調用的語法,使用LINQ的標準查詢運算符。
- 更靈活,支持復雜的查詢操作。
-
區別:
- 查詢語法在簡單查詢中更直觀和易讀。
- 方法語法在復雜查詢或需要自定義方法時更為強大。
-
示例:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };// 查詢語法 var querySyntax = from n in numberswhere n > 2select n;// 方法語法 var methodSyntax = numbers.Where(n => n > 2);// 兩者效果相同 foreach (var num in querySyntax) {Console.WriteLine(num); // 輸出: 3,4,5 }foreach (var num in methodSyntax) {Console.WriteLine(num); // 輸出: 3,4,5 }
50. 什么是梯形(Trapezoidal)規則?
答案:
梯形規則通常用于數值積分,是一種通過梯形近似來估計定積分的方法。由于梯形規則與C#中的LINQ無直接關聯,可能此問題意圖有誤。
更可能的面試問題: 什么是LINQ的SelectMany
運算符?
答案:
-
SelectMany
運算符用于將每個元素的子集合扁平化為一個單一的序列,常用于多層嵌套集合的查詢。 -
用途:
- 處理多維數據結構,如列表中的列表。
- 實現復合集合的平面化。
-
示例:
var listOfLists = new List<List<int>> {new List<int> {1, 2, 3},new List<int> {4, 5},new List<int> {6, 7, 8, 9} };var flattened = listOfLists.SelectMany(subList => subList);foreach (var num in flattened) {Console.WriteLine(num); // 輸出: 1,2,3,4,5,6,7,8,9 }
委托與事件
51. 什么是委托(Delegate)?它有什么用途?
答案:
-
**委托(Delegate)**是C#中一種類型安全的函數指針,允許將方法作為參數傳遞或賦值給變量。委托定義了方法的簽名(返回類型和參數列表),確保委托只能指向符合簽名的方法。
-
用途:
- 回調函數:在異步操作或事件處理中使用回調方法。
- 事件處理:作為事件的基礎類型,用于發布-訂閱模式。
- LINQ:通過委托實現查詢表達式中的選擇和過濾邏輯。
- 策略模式:動態選擇算法或行為。
-
示例:
public delegate void Notify(string message);public class Process {public event Notify OnCompleted;public void Start(){Console.WriteLine("Process started.");// 處理邏輯OnCompleted?.Invoke("Process finished.");} }// 使用 class Program {static void Main(string[] args){Process process = new Process();process.OnCompleted += ShowMessage;process.Start();}static void ShowMessage(string msg){Console.WriteLine(msg);} }// 輸出: // Process started. // Process finished.
52. 什么是多播委托(Multicast Delegate)?
答案:
-
**多播委托(Multicast Delegate)**是能夠封裝多個方法的委托實例。當調用多播委托時,所有封裝的方法都會按添加順序依次執行。委托的
+
和-
運算符用于組合或移除方法。 -
特點:
- 方法鏈:可以將多個方法組合成一個委托鏈。
- 執行順序:按添加順序依次執行各方法。
- 返回值:多播委托的返回值為最后一個方法的返回值,通常多播委托用于
void
返回類型的方法。
-
示例:
public delegate void Notify(string message);public class Publisher {public event Notify OnNotify;public void SendMessage(string msg){OnNotify?.Invoke(msg);} }public class Subscriber {public void ReceiveMessage(string msg){Console.WriteLine($"Received: {msg}");} }// 使用 class Program {static void Main(string[] args){Publisher publisher = new Publisher();Subscriber subscriber1 = new Subscriber();Subscriber subscriber2 = new Subscriber();publisher.OnNotify += subscriber1.ReceiveMessage;publisher.OnNotify += subscriber2.ReceiveMessage;publisher.SendMessage("Hello Subscribers!");// 輸出:// Received: Hello Subscribers!// Received: Hello Subscribers!} }
53. C#中如何實現事件(Event)?
答案:
-
**事件(Event)**是基于委托的一種機制,用于在對象之間傳遞通知,遵循發布-訂閱模式。事件允許多個訂閱者響應某個動作。
-
實現步驟:
- 定義一個委托類型,用于描述事件處理方法的簽名。
- 在發布者類中聲明事件,類型為該委托類型。
- 發布者在適當的時候觸發事件,通過調用事件委托。
- 訂閱者通過
+=
運算符訂閱事件,提供處理方法。
-
示例:
public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);public class ThresholdReachedEventArgs : EventArgs {public int Threshold { get; set; }public DateTime TimeReached { get; set; } }public class Counter {private int count = 0;private int threshold;public Counter(int threshold){this.threshold = threshold;}public event ThresholdReachedEventHandler ThresholdReached;public void Add(int x){count += x;if (count >= threshold){OnThresholdReached(new ThresholdReachedEventArgs { Threshold = threshold, TimeReached = DateTime.Now });}}protected virtual void OnThresholdReached(ThresholdReachedEventArgs e){ThresholdReached?.Invoke(this, e);} }// 使用 class Program {static void Main(string[] args){Counter c = new Counter(10);c.ThresholdReached += c_ThresholdReached;c.Add(3);c.Add(4);c.Add(5); // 觸發事件}static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e){Console.WriteLine($"Threshold of {e.Threshold} was reached at {e.TimeReached}.");} }// 輸出: // Threshold of 10 was reached at 2023-09-28 10:00:00.
54. 什么是委托鏈(Delegate Chain)?
答案:
-
委托鏈是指多個方法通過多播委托鏈接在一起,形成的一條調用鏈。當調用委托鏈時,所有鏈接的方法按順序被依次調用。
-
特點:
- 支持多播,可以同時執行多個方法。
- 常用于事件處理、回調函數等場景。
- 可以通過
+=
添加方法,通過-=
移除方法。
-
示例:
public delegate void Notify(string message);public void Method1(string msg) {Console.WriteLine("Method1: " + msg); }public void Method2(string msg) {Console.WriteLine("Method2: " + msg); }// 構建委托鏈 Notify notify = Method1; notify += Method2;notify("Hello Delegate Chain");// 輸出: // Method1: Hello Delegate Chain // Method2: Hello Delegate Chain
55. C#中的委托與函數指針有什么區別?
答案:
-
委托(Delegate):
- 是一種類型安全的、面向對象的函數引用。
- 可以封裝多個方法,支持多播。
- 支持異步調用、事件處理。
- 與C#的類型系統緊密集成,具有安全性和靈活性。
-
函數指針(Function Pointer):
- 是一種低級別的、非類型安全的指針,指向內存中的函數地址。
- 直接操作內存地址,存在安全風險。
- C# 9.0引入了
delegate*
語法,用于高性能場景,但仍不如委托安全。
-
區別:
- 類型安全:委托是類型安全的,函數指針不具備。
- 靈活性:委托更靈活,支持多播和異步操作。
- 安全性:委托提供更高的安全性,函數指針可能導致安全漏洞。
線程與并發
56. 什么是線程(Thread)?如何在C#中創建一個線程?
答案:
-
**線程(Thread)**是程序執行的最小單位,一個進程可以包含多個線程,共享進程的資源,如內存空間和文件句柄。
-
在C#中創建線程:
- 使用
System.Threading.Thread
類,傳入需要執行的方法,并調用Start
方法啟動線程。
- 使用
-
示例:
using System; using System.Threading;class Program {static void Main(string[] args){Thread t = new Thread(new ThreadStart(PrintNumbers));t.Start();// 主線程繼續執行for (int i = 0; i < 5; i++){Console.WriteLine("Main Thread: " + i);Thread.Sleep(500);}t.Join(); // 等待子線程完成}static void PrintNumbers(){for (int i = 0; i < 5; i++){Console.WriteLine("Child Thread: " + i);Thread.Sleep(500);}} }// 輸出(順序可能不同): // Child Thread: 0 // Main Thread: 0 // Child Thread: 1 // Main Thread: 1 // ...
57. 什么是線程池(Thread Pool)?它的優勢是什么?
答案:
-
**線程池(Thread Pool)**是預先創建并管理的一組線程,供應用程序重復利用,避免頻繁創建和銷毀線程的開銷。
-
優勢:
- 性能提升:重用線程,減少線程創建和銷毀帶來的性能開銷。
- 資源管理:集中管理線程資源,避免過多線程導致資源耗盡。
- 簡化編程:通過高級API(如
Task
,ThreadPool
)簡化異步編程模型。
-
在C#中的使用:
System.Threading.ThreadPool
類。- 高級抽象如
Task
和async/await
自動利用線程池。
-
示例:
using System; using System.Threading;class Program {static void Main(string[] args){ThreadPool.QueueUserWorkItem(DoWork);ThreadPool.QueueUserWorkItem(DoWork);Console.WriteLine("Main thread continues...");Thread.Sleep(1000); // 等待線程池線程完成}static void DoWork(object state){Console.WriteLine("ThreadPool thread: " + Thread.CurrentThread.ManagedThreadId);} }// 可能輸出: // Main thread continues... // ThreadPool thread: 3 // ThreadPool thread: 4
58. 什么是鎖(Lock)?如何在C#中實現鎖機制?
答案:
-
**鎖(Lock)**是一種同步機制,用于保護共享資源,防止多個線程同時訪問和修改,導致數據競態(Race Condition)。
-
在C#中實現鎖機制:
- 使用
lock
關鍵字(語法糖,基于Monitor
類)。 lock
關鍵字確保被保護的代碼塊在同一時間只能被一個線程訪問。
- 使用
-
示例:
using System; using System.Threading;class Counter {private int count = 0;private object lockObj = new object();public void Increment(){lock (lockObj){count++;Console.WriteLine("Count: " + count);}} }class Program {static void Main(string[] args){Counter counter = new Counter();for (int i = 0; i < 5; i++){new Thread(counter.Increment).Start();}} }// 輸出: Count: 1 // Count: 2 // Count: 3 // Count: 4 // Count: 5
-
注意事項:
- 鎖定對象應私有且只用于鎖定,不應鎖定
this
或公共對象。 - 盡量縮小鎖定范圍,避免死鎖和性能問題。
- 鎖定對象應私有且只用于鎖定,不應鎖定
59. 什么是死鎖(Deadlock)?如何避免?
答案:
-
**死鎖(Deadlock)**是指兩個或多個線程相互等待對方釋放資源,導致所有線程永久阻塞,無法繼續執行。
-
產生條件(Coffman條件):
- 互斥:至少有一個資源必須處于非共享模式。
- 保持和等待:至少有一個線程保持一個資源并等待獲取另一個被其他線程占用的資源。
- 不剝奪:資源在未釋放前,不能被強行剝奪。
- 循環等待:存在一個線程環,環中每個線程都在等待下一個線程持有的資源。
-
避免策略:
- 資源排序:為所有資源定義一個全局順序,線程必須按順序請求資源,避免循環等待。
- 避免保持和等待:線程在請求新資源前,釋放當前持有的所有資源。
- 使用超時:線程在請求資源時設置超時,如果超時則放棄并重試。
- 減少鎖的范圍:盡量縮小鎖定代碼塊的范圍,減少持有鎖的時間。
- 使用死鎖檢測:定期檢查系統中是否存在死鎖,采取恢復措施。
-
示例避免資源排序:
class ResourceA { } class ResourceB { }class DeadlockDemo {private ResourceA resourceA = new ResourceA();private ResourceB resourceB = new ResourceB();public void Method1(){lock (resourceA){Console.WriteLine("Thread 1: Locked ResourceA");Thread.Sleep(100);lock (resourceB){Console.WriteLine("Thread 1: Locked ResourceB");}}}public void Method2(){lock (resourceA) // 改為先鎖定ResourceA,再鎖定ResourceB{Console.WriteLine("Thread 2: Locked ResourceA");Thread.Sleep(100);lock (resourceB){Console.WriteLine("Thread 2: Locked ResourceB");}}} }// 在主線程中啟動兩個線程調用Method1和Method2,避免死鎖
60. 什么是lock
與Monitor
的區別和聯系?
答案:
-
聯系:
lock
關鍵字是C#的語法糖,底層實現基于System.Threading.Monitor
類。- 兩者都用于實現線程同步,確保共享資源的互斥訪問。
-
區別:
- 簡潔性:
lock
語法更簡潔,自動處理進入和退出鎖的過程,包括異常時的釋放鎖。 - 功能性:
Monitor
類提供了更豐富的功能,如Pulse
和Wait
方法,用于線程間的信號傳遞和協作。 - 異常處理:使用
lock
時,無需顯式釋放鎖,Monitor
需要確保在finally
塊中調用Monitor.Exit
釋放鎖。
- 簡潔性:
-
示例:
-
使用
lock
:private object lockObj = new object();public void SafeMethod() {lock (lockObj){// 臨界區} }
-
使用
Monitor
:private object lockObj = new object();public void SafeMethod() {bool lockTaken = false;try{Monitor.Enter(lockObj, ref lockTaken);// 臨界區}finally{if (lockTaken)Monitor.Exit(lockObj);} }
-
異步與并發
61. 什么是async
方法的返回類型,可以有哪些類型?
答案:
-
async
方法需要有特定的返回類型,用于表示異步操作的結果或狀態。 -
可能的返回類型:
-
Task
:- 用于沒有返回值的異步方法。
示例:
public async Task DoWorkAsync() {await Task.Delay(1000);Console.WriteLine("Work completed."); }
-
Task<T>
:- 用于有返回值的異步方法。
示例:
public async Task<int> GetNumberAsync() {await Task.Delay(1000);return 42; }
-
void
:- 用于異步事件處理器,不建議在其他情況下使用,因為無法等待或捕獲異常。
示例:
public async void OnButtonClick(object sender, EventArgs e) {await Task.Delay(1000);Console.WriteLine("Button clicked."); }
-
62. 什么是CancellationToken
,如何在異步操作中使用?
答案:
-
**
CancellationToken
**是一種機制,用于通知異步操作或任務取消其執行。它通過CancellationTokenSource
傳遞,并在需要的地方檢查取消請求。 -
使用步驟:
- 創建
CancellationTokenSource
:發起取消請求的源。 - 獲取
CancellationToken
:從CancellationTokenSource
獲取令牌。 - 傳遞
CancellationToken
:將令牌傳遞給異步方法或任務。 - 在異步方法中檢查取消:通過
token.IsCancellationRequested
或token.ThrowIfCancellationRequested()
主動檢查并響應取消請求。 - 發起取消:調用
CancellationTokenSource.Cancel()
發起取消。
- 創建
-
示例:
using System; using System.Threading; using System.Threading.Tasks;class Program {static async Task Main(string[] args){CancellationTokenSource cts = new CancellationTokenSource();Task task = LongRunningOperationAsync(cts.Token);// 取消任務cts.Cancel();try{await task;}catch (OperationCanceledException){Console.WriteLine("Operation was canceled.");}}static async Task LongRunningOperationAsync(CancellationToken token){for (int i = 0; i < 10; i++){token.ThrowIfCancellationRequested();Console.WriteLine($"Working... {i}");await Task.Delay(1000);}} }// 輸出: // Working... 0 // 操作被取消后,捕獲異常并輸出: // Operation was canceled.
63. 什么是Task Parallel Library
(TPL)?
答案:
-
**Task Parallel Library(TPL)**是.NET框架提供的一組用于簡化并行和異步編程的庫,位于
System.Threading.Tasks
命名空間。 -
主要特性:
- 任務(Task):抽象并行操作的單位,支持組合、等待和取消。
- 數據并行:通過
Parallel
類實現對集合的并行操作,如Parallel.For
,Parallel.ForEach
。 - PLINQ(Parallel LINQ):對LINQ查詢進行并行化處理,提升查詢性能。
- 任務調度:自動管理線程池,優化資源使用。
-
優勢:
- 簡化并行編程模型。
- 提高代碼的可讀性和可維護性。
- 利用多核處理器提高應用性能。
-
示例:
using System; using System.Threading.Tasks;class Program {static void Main(string[] args){Task.Run(() => DoWork());Parallel.For(0, 10, i =>{Console.WriteLine($"Parallel task {i}");});Console.ReadLine();}static void DoWork(){Console.WriteLine("Task is running...");} }// 輸出: // Task is running... // Parallel task 0 // Parallel task 1 // ... // Parallel task 9
64. 什么是async
和await
的配對使用模式?
答案:
-
配對使用模式:
- 方法使用
async
修飾,標記為異步方法。 - 在異步方法內部,使用
await
關鍵字等待一個返回Task
或Task<T>
的異步操作完成。 - 異步方法的調用方可以選擇使用
await
等待其完成,或繼續執行其他操作。
- 方法使用
-
關鍵點:
async
修飾符使方法能夠使用await
,改變方法的編譯方式,使其返回一個任務。await
關鍵字釋放當前線程,等待任務完成后繼續執行,確保異步操作的非阻塞性。
-
示例:
public async Task<int> CalculateSumAsync(int a, int b) {await Task.Delay(1000); // 模擬耗時操作return a + b; }public async Task DisplaySumAsync() {int sum = await CalculateSumAsync(5, 10);Console.WriteLine($"Sum: {sum}"); }// 使用 await DisplaySumAsync(); // 輸出: Sum: 15 (延遲1秒)
65. 什么是異步流(Asynchronous Streams)?
答案:
-
**異步流(Asynchronous Streams)**是C# 8.0引入的一種特性,允許異步地遍歷數據序列,結合
async
和yield
實現異步的迭代器。 -
用途:
- 處理大規模或無限的數據流,節省內存和提高效率。
- 結合I/O操作,如從網絡或文件異步讀取數據。
-
關鍵字:
async
和await
。IAsyncEnumerable<T>
和IAsyncEnumerator<T>
接口。await foreach
語法。
-
示例:
public async IAsyncEnumerable<int> GetNumbersAsync() {for (int i = 0; i < 5; i++){await Task.Delay(500); // 模擬異步操作yield return i;} }public async Task DisplayNumbersAsync() {await foreach (var num in GetNumbersAsync()){Console.WriteLine(num);} }// 使用 await DisplayNumbersAsync();// 輸出(每500ms輸出一個數字): // 0 // 1 // 2 // 3 // 4
66. 什么是TPL中的Task.WhenAll
和Task.WhenAny
?
答案:
-
Task.WhenAll
:- 接收一組任務,返回一個在所有任務完成時完成的任務。
- 如果任何一個任務失敗,則
WhenAll
任務也會失敗。 - 通常用于等待多個任務同時完成。
-
Task.WhenAny
:- 接收一組任務,返回一個在任意一個任務完成時完成的任務。
- 允許在最先完成的任務時進行響應。
-
示例:
using System; using System.Threading.Tasks;class Program {static async Task Main(string[] args){Task<int> task1 = Task.Run(() => {Task.Delay(1000).Wait();return 1;});Task<int> task2 = Task.Run(() => {Task.Delay(2000).Wait();return 2;});Task<int[]> allTasks = Task.WhenAll(task1, task2);int[] results = await allTasks;Console.WriteLine($"Results: {string.Join(", ", results)}"); // 輸出: Results: 1, 2Task firstTask = Task.WhenAny(task1, task2);await firstTask;Console.WriteLine("First task completed.");} }
67. 如何使用async
和await
處理并行任務?
答案:
可以通過啟動多個異步任務,并使用await Task.WhenAll
等待所有任務并行完成,實現并行異步操作。
示例:
public async Task ParallelTasksAsync()
{Task<int> task1 = Task.Run(() => {Task.Delay(1000).Wait();return 1;});Task<int> task2 = Task.Run(() => {Task.Delay(1500).Wait();return 2;});Task<int> task3 = Task.Run(() => {Task.Delay(500).Wait();return 3;});int[] results = await Task.WhenAll(task1, task2, task3);Console.WriteLine($"Results: {string.Join(", ", results)}"); // 輸出: Results: 1, 2, 3
}// 使用
await ParallelTasksAsync();
68. 什么是ValueTask
,它與Task
有什么區別?
答案:
-
**
ValueTask
**是C# 7.0引入的一種新的異步返回類型,用于替代Task
,尤其在方法可能頻繁完成同步而無需異步操作時,提供更高的性能。 -
區別:
- 性能:
ValueTask
避免了在頻繁同步完成的情況創建大量Task
對象,減少堆分配和GC壓力。 - 多次使用:
ValueTask
只能作為單次異步操作使用,不能被多次等待或轉換為Task
。 - 語義:
ValueTask
表達異步操作可能是同步完成的,需謹慎使用。
- 性能:
-
使用場景:
- 高性能庫或框架中用于優化異步方法返回類型。
- 方法可能在大多數情況下同步完成,偶爾異步。
-
示例:
public async ValueTask<int> GetValueAsync(bool returnSync) {if (returnSync){return 42; // 同步完成}else{await Task.Delay(1000);return 99;} }// 使用 int value1 = await GetValueAsync(true); // 快速返回 int value2 = await GetValueAsync(false); // 異步等待
69. 什么是防火墻(Firewall)?
答案:
**防火墻(Firewall)**通常指網絡安全設備或軟件,用于監控和控制進出網絡的數據流。它基于預定義的安全規則,允許或阻止特定類型的網絡流量,以保護內部網絡免受未授權訪問和網絡攻擊。
-
作用:
- 保護計算機和網絡免受惡意攻擊。
- 控制和過濾進出網絡的數據流。
- 監控網絡活動,檢測異常行為。
-
類型:
- 網絡防火墻:部署在網絡邊界,過濾進出網絡的數據包。
- 主機型防火墻:安裝在單個計算機上,控制該計算機的網絡流量。
- 應用層防火墻:針對特定應用程序的流量進行過濾和監控。
-
特點:
- 基于規則的過濾(如IP地址、端口號、協議)。
- 支持狀態檢測,理解連接的上下文。
- 提供日志記錄和報警功能。
注意:由于此問題與C#面試關聯不大,可能屬于誤提或基礎網絡知識相關面試問題。
70. 什么是線程安全(Thread Safety)?
答案:
-
**線程安全(Thread Safety)**指代碼或數據結構能夠在多線程環境中安全地執行,不會引發數據競態(Race Condition)或不一致。
-
實現線程安全的方法:
- 使用鎖(Lock):通過
lock
關鍵字或Monitor
類,實現對共享資源的互斥訪問。 - 使用并發集合:如
ConcurrentDictionary
,ConcurrentQueue
等,內置線程安全機制。 - 不可變對象:設計對象為不可變,避免并發修改。
- 使用原子操作:通過
Interlocked
類提供的原子操作方法,如Interlocked.Increment
。 - 避免共享狀態:盡量減少或避免多個線程訪問同一數據。
- 使用鎖(Lock):通過
-
示例:
public class ThreadSafeCounter {private int count = 0;private object lockObj = new object();public void Increment(){lock (lockObj){count++;}}public int GetCount(){lock (lockObj){return count;}} }
異常處理與調試
71. 如何在C#中進行調試?
答案:
-
使用集成開發環境(IDE):
- Visual Studio提供豐富的調試工具,如斷點、步進執行、監視變量、調用堆棧等。
-
斷點(Breakpoints):
- 在代碼行左側點擊設置斷點,使程序在執行到該行時暫停,便于檢查狀態。
-
步進執行:
- Step Into (F11):進入方法內部逐行執行。
- Step Over (F10):執行方法但不進入內部。
- Step Out (Shift+F11):從當前方法退出。
-
監視和即時窗口:
- 監視變量的值,通過“Watch”窗口或“Immediate”窗口查詢和修改變量。
-
條件斷點和日志斷點:
- 設置斷點滿足特定條件時觸發,或僅記錄日志而不中斷執行。
-
異常設置:
- 配置IDE在異常拋出時自動中斷,便于捕捉未處理的異常。
-
遠程調試:
- 在不同機器上運行的應用程序進行調試。
72. 如何捕獲未處理的異常?
答案:
-
在控制臺應用程序中:
- 使用
AppDomain.CurrentDomain.UnhandledException
事件捕獲未處理的異常。
示例:
class Program {static void Main(string[] args){AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;throw new Exception("未處理的異常");}static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e){Console.WriteLine("捕獲到未處理的異常: " + ((Exception)e.ExceptionObject).Message);} }
- 使用
-
在應用程序域中:
- 使用
Application.ThreadException
事件(適用于WinForms應用)。
- 使用
-
訂閱TaskScheduler.UnobservedTaskException(適用于異步任務中的未觀察異常)。
73. 什么是finally
塊在異常處理中的作用?
答案:
-
finally
塊包含在try-catch-finally
結構中,用于在try
塊中執行完畢后,無論是否發生異常,都執行的清理代碼。 -
作用:
- 確保資源被正確釋放,如關閉文件、釋放鎖、銷毀對象等。
- 保證必要的操作在異常后也能被執行,維持程序的穩定性。
-
示例:
try {// 可能引發異常的代碼 } catch (Exception ex) {Console.WriteLine("捕獲異常: " + ex.Message); } finally {// 清理操作,無論是否發生異常都會執行Console.WriteLine("執行 finally 塊"); }
74. 如何自定義異常類?
答案:
-
步驟:
- 創建一個繼承自
Exception
或其派生類的類。 - 實現至少一個構造函數,通常包括無參構造函數、帶消息參數的構造函數,以及支持序列化的構造函數(可選)。
- 創建一個繼承自
-
示例:
[Serializable] public class InvalidInputException : Exception {public InvalidInputException() { }public InvalidInputException(string message) : base(message) { }public InvalidInputException(string message, Exception inner) : base(message, inner) { }protected InvalidInputException(SerializationInfo info, StreamingContext context) : base(info, context) { } }// 使用 public void ValidateInput(int input) {if (input < 0)throw new InvalidInputException("輸入值不能為負數。"); }
75. 什么是try-finally
結構?
答案:
-
try-finally
結構是一種異常處理結構,包含try
代碼塊和finally
代碼塊。其中,finally
代碼塊在try
代碼塊執行完畢后,無論是否發生異常,都會執行。 -
作用:
- 確保必要的清理操作在異常發生時也能被執行。
- 比較適用于無需捕獲異常但需要執行清理操作的場景。
-
示例:
try {// 執行操作,可能引發異常Console.WriteLine("執行 try 塊");throw new Exception("錯誤"); } finally {// 執行清理操作Console.WriteLine("執行 finally 塊"); }// 輸出: // 執行 try 塊 // 執行 finally 塊 // 拋出異常
設計模式
76. 什么是設計模式?C#中常用的設計模式有哪些?
答案:
-
**設計模式(Design Patterns)**是經驗豐富的軟件開發者在解決特定問題時總結出的通用解決方案,提供了系統化、可復用的設計方法。
-
常用的設計模式按其用途分類,主要包括:
-
創建型模式:
- 單例模式(Singleton)
- 工廠方法模式(Factory Method)
- 抽象工廠模式(Abstract Factory)
- 建造者模式(Builder)
- 原型模式(Prototype)
-
結構型模式:
- 適配器模式(Adapter)
- 裝飾器模式(Decorator)
- 組合模式(Composite)
- 外觀模式(Facade)
- 代理模式(Proxy)
- 橋接模式(Bridge)
- 享元模式(Flyweight)
-
行為型模式:
- 策略模式(Strategy)
- 觀察者模式(Observer)
- 狀態模式(State)
- 模板方法模式(Template Method)
- 職責鏈模式(Chain of Responsibility)
- 命令模式(Command)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 備忘錄模式(Memento)
- 解釋器模式(Interpreter)
- 訪問者模式(Visitor)
-
-
應用場景:
- 提高代碼的復用性和可維護性。
- 提供解決特定設計問題的標準方法。
- 增強系統的靈活性和擴展性。
77. 什么是單例模式(Singleton Pattern)?如何實現?
答案:
-
**單例模式(Singleton Pattern)**確保一個類只有一個實例,并提供一個全局訪問點。
-
實現方式:
- 私有構造函數:防止外部創建實例。
- 靜態字段:保存單例實例。
- 公共靜態屬性或方法:提供獲取實例的途徑。
- 線程安全:確保多線程環境下只有一個實例。
-
C#實現(線程安全的懶加載方式):
public sealed class Singleton {private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());public static Singleton Instance { get { return lazy.Value; } }private Singleton(){// 私有構造函數,防止外部實例化}public void DoSomething(){Console.WriteLine("Singleton instance is doing something.");} }// 使用 Singleton.Instance.DoSomething();
說明:
Lazy<T>
提供延遲初始化,并確保線程安全。sealed
關鍵字防止類被繼承,確保單例特性不被破壞。
78. 什么是工廠方法模式(Factory Method Pattern)?
答案:
-
**工廠方法模式(Factory Method Pattern)**屬于創建型設計模式,通過定義一個創建對象的接口,讓子類決定實例化哪一個類。使得類的實例化延遲到子類。
-
目的:
- 提高代碼的可擴展性和靈活性。
- 解耦對象的創建與使用。
-
關鍵點:
- 抽象類或接口定義工廠方法。
- 具體工廠類實現工廠方法,創建具體產品實例。
-
示例:
// 產品接口 public interface IProduct {void Operation(); }// 具體產品A public class ConcreteProductA : IProduct {public void Operation(){Console.WriteLine("Operation of ConcreteProductA");} }// 具體產品B public class ConcreteProductB : IProduct {public void Operation(){Console.WriteLine("Operation of ConcreteProductB");} }// 工廠接口 public abstract class Creator {public abstract IProduct FactoryMethod(); }// 具體工廠A public class ConcreteCreatorA : Creator {public override IProduct FactoryMethod(){return new ConcreteProductA();} }// 具體工廠B public class ConcreteCreatorB : Creator {public override IProduct FactoryMethod(){return new ConcreteProductB();} }// 使用 class Program {static void Main(string[] args){Creator creatorA = new ConcreteCreatorA();IProduct productA = creatorA.FactoryMethod();productA.Operation(); // 輸出: Operation of ConcreteProductACreator creatorB = new ConcreteCreatorB();IProduct productB = creatorB.FactoryMethod();productB.Operation(); // 輸出: Operation of ConcreteProductB} }
79. 什么是觀察者模式(Observer Pattern)?
答案:
-
**觀察者模式(Observer Pattern)**屬于行為型設計模式,定義了一種一對多的依賴關系,當一個對象(被觀察者)狀態發生變化時,所有依賴于它的對象(觀察者)都會被自動通知和更新。
-
目的:
- 實現松散耦合,減少對象之間的依賴。
- 支持動態添加和移除觀察者。
-
關鍵點:
- Subject(被觀察者):維護一組觀察者,并提供注冊、注銷、通知的方法。
- Observer(觀察者):定義一個更新接口,供被觀察者調用。
-
示例:
using System; using System.Collections.Generic;// 觀察者接口 public interface IObserver {void Update(string message); }// 被觀察者類 public class Subject {private List<IObserver> observers = new List<IObserver>();public void Attach(IObserver observer){observers.Add(observer);}public void Detach(IObserver observer){observers.Remove(observer);}public void Notify(string message){foreach (var observer in observers){observer.Update(message);}} }// 具體觀察者 public class ConcreteObserver : IObserver {private string name;public ConcreteObserver(string name){this.name = name;}public void Update(string message){Console.WriteLine($"{name} received message: {message}");} }// 使用 class Program {static void Main(string[] args){Subject subject = new Subject();IObserver observer1 = new ConcreteObserver("Observer1");IObserver observer2 = new ConcreteObserver("Observer2");subject.Attach(observer1);subject.Attach(observer2);subject.Notify("Hello Observers!");// 輸出:// Observer1 received message: Hello Observers!// Observer2 received message: Hello Observers!} }
80. 什么是裝飾器模式(Decorator Pattern)?
答案:
-
**裝飾器模式(Decorator Pattern)**屬于結構型設計模式,允許動態地向對象添加職責,提供比繼承更靈活的擴展方式。
-
目的:
- 動態地組合對象的功能。
- 避免大量的子類,提升系統的靈活性。
-
關鍵點:
- 組件接口:定義具體組件和裝飾器的共同行為。
- 具體組件:實現組件接口,表示被裝飾的對象。
- 裝飾器基類:包含一個組件接口的引用,轉發行為。
- 具體裝飾器:繼承裝飾器基類,添加額外功能。
-
示例:
// 組件接口 public interface INotifier {void Send(string message); }// 具體組件 public class EmailNotifier : INotifier {public void Send(string message){Console.WriteLine($"Sending Email: {message}");} }// 裝飾器基類 public abstract class NotifierDecorator : INotifier {protected INotifier notifier;public NotifierDecorator(INotifier notifier){this.notifier = notifier;}public virtual void Send(string message){notifier.Send(message);} }// 具體裝飾器A public class SMSNotifier : NotifierDecorator {public SMSNotifier(INotifier notifier) : base(notifier) { }public override void Send(string message){base.Send(message);Console.WriteLine($"Sending SMS: {message}");} }// 具體裝飾器B public class PushNotifier : NotifierDecorator {public PushNotifier(INotifier notifier) : base(notifier) { }public override void Send(string message){base.Send(message);Console.WriteLine($"Sending Push Notification: {message}");} }// 使用 class Program {static void Main(string[] args){INotifier notifier = new EmailNotifier();notifier = new SMSNotifier(notifier);notifier = new PushNotifier(notifier);notifier.Send("Hello Decorators!");// 輸出:// Sending Email: Hello Decorators!// Sending SMS: Hello Decorators!// Sending Push Notification: Hello Decorators!} }
81. 什么是工廠模式(Factory Pattern)的優點和缺點?
答案:
-
優點:
- 解耦:客戶端無需知道具體的創建類,只需依賴于抽象接口,減少類之間的耦合。
- 可擴展性:容易引入新的產品類,只需創建新的工廠方法或工廠類,無需修改現有代碼。
- 單一職責:將對象的創建邏輯集中到工廠,符合單一職責原則。
-
缺點:
- 增加類的數量:需要為每個產品類創建對應的工廠,可能導致類數量增加,增加復雜性。
- 維護成本:隨著產品種類的增多,工廠方法或工廠類的維護變得更加困難。
- 設計復雜性:對于簡單的對象創建,使用工廠模式可能顯得冗余。
-
適用場景:
- 需要創建的對象數量龐大且復雜,難以通過簡單的構造函數完成。
- 客戶端不依賴于具體的對象類,依賴于抽象接口。
- 需要在運行時動態決定創建哪種類型的對象。
82. 什么是單例模式的餓漢式(Eager Initialization)和懶漢式(Lazy Initialization)?
答案:
-
餓漢式(Eager Initialization):
- 在類加載時就創建單例實例,確保線程安全。
- 實現簡單,但可能造成資源浪費,尤其在單例未被使用的情況下。
實現示例:
public sealed class Singleton {private static readonly Singleton instance = new Singleton();public static Singleton Instance { get { return instance; } }private Singleton(){// 私有構造函數} }
-
懶漢式(Lazy Initialization):
- 在首次需要實例時創建,延遲實例化,節約資源。
- 需保證線程安全,可以通過
Lazy<T>
或使用鎖(lock
)實現。
使用
Lazy<T>
的實現示例:public sealed class Singleton {private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());public static Singleton Instance { get { return lazy.Value; } }private Singleton(){// 私有構造函數} }
83. 什么是策略模式(Strategy Pattern)?
答案:
-
**策略模式(Strategy Pattern)**屬于行為型設計模式,定義了一系列算法,將每個算法封裝起來,使它們可以互換。策略模式使得算法的變化獨立于使用算法的客戶端。
-
目的:
- 提高算法的可復用性和靈活性。
- 避免在類中使用大量的條件語句。
- 支持動態切換算法。
-
關鍵點:
- 策略接口:定義算法的統一接口。
- 具體策略類:實現策略接口的具體算法。
- 上下文類:持有策略接口的引用,通過上下文類執行策略。
-
示例:
// 策略接口 public interface ICompressionStrategy {void Compress(string fileName); }// 具體策略A public class ZipCompression : ICompressionStrategy {public void Compress(string fileName){Console.WriteLine($"Compressing {fileName} using ZIP compression.");} }// 具體策略B public class RarCompression : ICompressionStrategy {public void Compress(string fileName){Console.WriteLine($"Compressing {fileName} using RAR compression.");} }// 上下文類 public class CompressionContext {private ICompressionStrategy strategy;public void SetStrategy(ICompressionStrategy strategy){this.strategy = strategy;}public void CreateArchive(string fileName){if (strategy == null){Console.WriteLine("Compression strategy not set.");return;}strategy.Compress(fileName);} }// 使用 class Program {static void Main(string[] args){CompressionContext context = new CompressionContext();context.SetStrategy(new ZipCompression());context.CreateArchive("file1.txt"); // 輸出: Compressing file1.txt using ZIP compression.context.SetStrategy(new RarCompression());context.CreateArchive("file2.txt"); // 輸出: Compressing file2.txt using RAR compression.} }
84. 解釋一下觀察者模式中的Subject
和Observer
角色。
答案:
-
Subject(被觀察者):
- 是觀察者模式中的核心實體,保持一組觀察者的引用,允許觀察者注冊和注銷。
- 負責在自身狀態發生變化時,通知所有注冊的觀察者。
-
Observer(觀察者):
- 是觀察者模式中的依賴實體,實現特定的接口,以接收來自被觀察者的通知。
- 通常包含一個更新方法,用于在被觀察者狀態變化時執行相應的操作。
-
關系:
- 一個Subject可以有多個Observer。
- Observers依賴于Subject,但Subject不依賴于具體的Observer,實現松散耦合。
-
示例:
// 已在觀察者模式第15題中展示
85. 什么是裝飾器模式與代理模式的區別?
答案:
-
裝飾器模式(Decorator Pattern):
- 目的:動態地向對象添加額外的職責或功能,而無需修改原有類。
- 結構:基于組合,通過裝飾器類包裹被裝飾對象,實現功能的擴展。
- 應用場景:需要在運行時靈活地為對象添加或移除功能。
-
代理模式(Proxy Pattern):
- 目的:提供一個替代對象,控制對另一個對象的訪問。
- 結構:代理類實現與目標對象相同的接口,通過代理控制對目標對象的訪問。
- 應用場景:需要對目標對象的訪問進行控制,如權限驗證、懶加載、遠程代理等。
-
區別:
- 意圖不同:裝飾器用于功能增強,代理用于控制訪問。
- 關系:裝飾器通常保留對被裝飾對象的引用,而代理通常保持對目標對象的引用,執行訪問控制。
-
示例:
- 裝飾器:為圖像對象添加濾鏡效果。
- 代理:為遠程服務提供本地代理,控制網絡訪問。
異步編程進階
86. 什么是async void
,它的使用場景是什么?
答案:
-
**
async void
**是異步方法的一個返回類型,表示該方法不返回任務,異步操作無法被調用方等待或進行異常捕獲。 -
使用場景:
- 主要用于事件處理器,因為事件處理程序需要返回
void
。
- 主要用于事件處理器,因為事件處理程序需要返回
-
注意事項:
- 避免在其他場景中使用
async void
,因其無法等待或捕獲異常,容易導致未處理的異常和程序崩潰。 - Prefer using
Task
orTask<T>
作為異步方法的返回類型。
- 避免在其他場景中使用
-
示例:
public async void Button_Click(object sender, EventArgs e) {try{await SomeAsyncOperation();}catch (Exception ex){// 異常可以在這里捕獲Console.WriteLine(ex.Message);} }
87. 什么是同步上下文(SynchronizationContext)?
答案:
-
**同步上下文(SynchronizationContext)**是.NET中的一個抽象類,用于捕獲和管理異步操作的上下文信息,如線程調度和回調執行的位置。
-
作用:
- 決定異步操作完成后回調執行的線程或上下文。
- 支持在特定的上下文(如UI線程)中執行回調,避免線程切換問題。
-
應用:
- 在GUI應用(如WPF、WinForms)中,確保UI更新在主線程執行。
- 在ASP.NET中,管理請求上下文,確保異步操作的正確執行。
-
示例:
// 在WPF應用中,UI線程有自己的同步上下文 public async void LoadData() {// 整個方法在UI線程執行var data = await GetDataAsync(); // await后,繼續在UI線程執行// 更新UI控件myLabel.Content = data; }
88. 如何取消一個異步任務?
答案:
-
使用
CancellationToken
:- 創建
CancellationTokenSource
,獲取CancellationToken
。 - 將
CancellationToken
傳遞給異步方法或任務。 - 在需要取消時,調用
CancellationTokenSource.Cancel()
方法。 - 在異步方法中,定期檢查
CancellationToken
是否請求取消,并適當響應。
- 創建
-
示例:
using System; using System.Threading; using System.Threading.Tasks;class Program {static async Task Main(string[] args){CancellationTokenSource cts = new CancellationTokenSource();Task task = LongRunningOperationAsync(cts.Token);// 等待一段時間后取消await Task.Delay(2000);cts.Cancel();try{await task;}catch (OperationCanceledException){Console.WriteLine("任務已取消。");}}static async Task LongRunningOperationAsync(CancellationToken token){for (int i = 0; i < 10; i++){token.ThrowIfCancellationRequested();Console.WriteLine($"執行步驟 {i}");await Task.Delay(1000);}} }// 輸出(約2秒后): // 執行步驟 0 // 執行步驟 1 // 任務已取消。
89. 什么是任務取消(Task Cancellation)中的協作式取消?
答案:
-
**協作式取消(Cooperative Cancellation)**指任務自己檢查是否有取消請求,并主動中止執行。任務必須遵循取消策略,通過檢查
CancellationToken
并適時退出。 -
特點:
- 任務需要設計為可取消,頻繁檢查取消請求。
- 提高取消的響應性和優雅性,避免強制中斷導致的不一致狀態。
-
實現方法:
- 使用
CancellationToken
,在任務內部定期檢查token.IsCancellationRequested
或調用token.ThrowIfCancellationRequested()
。
- 使用
-
示例:
public async Task DoWorkAsync(CancellationToken token) {for (int i = 0; i < 100; i++){// 檢查取消請求if (token.IsCancellationRequested){Console.WriteLine("任務取消中...");break;}// 執行工作Console.WriteLine($"工作步驟 {i}");await Task.Delay(500, token); // 支持取消的延遲}Console.WriteLine("任務完成。"); }// 使用 CancellationTokenSource cts = new CancellationTokenSource(); Task workTask = DoWorkAsync(cts.Token);// 取消任務 cts.CancelAfter(3000); // 3秒后取消await workTask;// 輸出: // 工作步驟 0 // 工作步驟 1 // 工作步驟 2 // 工作步驟 3 // 任務取消中... // 任務完成。
90. 什么是并行LINQ(PLINQ)?
答案:
-
**并行LINQ(PLINQ)**是LINQ的一個擴展,允許在多核處理器上并行執行查詢操作,通過分段處理和線程分配提高查詢性能。
-
特點:
- 利用多核優勢,加速大規模數據處理。
- 支持查詢操作符,如
Where
,Select
,OrderBy
等。 - 自動優化線程使用,無需手動管理線程。
-
使用方式:
- 將數據源轉換為
AsParallel()
,啟用并行查詢。 - 可選擇性地使用
WithDegreeOfParallelism
指定并行度。
- 將數據源轉換為
-
示例:
using System; using System.Linq;class Program {static void Main(string[] args){var numbers = Enumerable.Range(1, 1000000);// 并行LINQ查詢var evenNumbers = numbers.AsParallel().Where(n => n % 2 == 0).ToList();Console.WriteLine($"找到 {evenNumbers.Count} 個偶數。");} }
-
注意事項:
- 并非所有查詢都適合并行執行,需考慮數據量、操作復雜性和開銷。
- 并行查詢可能導致結果順序不同,除非使用
AsOrdered()
保證順序。 - 處理副作用操作時需謹慎,避免數據競態。
異常處理進階
91. 什么是try-catch-finally
結構中的catch
參數?
答案:
-
catch
參數是表示被捕獲異常的變量,可以用于訪問異常的信息,如消息、堆棧跟蹤等。 -
語法:
catch (Exception ex) {// 使用 ex 變量Console.WriteLine(ex.Message);Console.WriteLine(ex.StackTrace); }
-
特點:
- 可以捕獲特定類型的異常,通過定義為特定異常類的類型。
- 支持基于異常類型的多層次捕獲,優先捕獲更具體的異常類型。
-
示例:
try {int[] array = {1, 2, 3};Console.WriteLine(array[5]); // 引發IndexOutOfRangeException } catch (IndexOutOfRangeException ex) {Console.WriteLine("捕獲到索引越界異常: " + ex.Message); } catch (Exception ex) {Console.WriteLine("捕獲到其他異常: " + ex.Message); }
92. 如何在C#中創建自定義的異常鏈?
答案:
-
**異常鏈(Exception Chaining)**是通過在拋出新異常時,將原始異常作為內部異常傳遞,從而保留異常的上下文信息。
-
實現方法:
- 在自定義異常類的構造函數中接受一個
Exception
類型的參數,傳遞給基類的構造函數。 - 使用
throw new Exception("message", ex);
語法拋出帶有內部異常的異常對象。
- 在自定義異常類的構造函數中接受一個
-
示例:
public class DataProcessingException : Exception {public DataProcessingException() { }public DataProcessingException(string message) : base(message) { }public DataProcessingException(string message, Exception inner) : base(message, inner) { } }public void ProcessData() {try{// 可能引發異常的操作int x = int.Parse("abc"); // 引發FormatException}catch (FormatException ex){throw new DataProcessingException("數據格式錯誤。", ex);} }// 使用 try {ProcessData(); } catch (DataProcessingException ex) {Console.WriteLine(ex.Message); // 輸出: 數據格式錯誤。Console.WriteLine("內部異常: " + ex.InnerException.Message); // 輸出: 內部異常: Input string was not in a correct format. }
93. 什么是Throw
關鍵字的用法?
答案:
-
throw
關鍵字用于在代碼中主動引發異常,可以在try
塊中拋出新的異常或在catch
塊中重新拋出捕獲的異常。 -
用法:
-
拋出新異常:
throw new ArgumentNullException(nameof(parameter), "參數不能為空。");
-
重新拋出捕獲的異常:
catch (Exception ex) {// 處理部分邏輯throw; // 重拋當前捕獲的異常,保留堆棧信息 }
-
-
區別:
throw;
:在catch
塊中重新拋出當前異常,保留原始堆棧信息。throw ex;
:在catch
塊中拋出一個新的異常,丟失原始的堆棧信息。
-
注意事項:
- 應優先使用
throw;
重新拋出異常,以保留異常的完整上下文。 - 不應在非異常處理上下文中隨意使用
throw
,以免造成程序崩潰。
- 應優先使用
94. 如何在C#中使用try-catch
嵌套?
答案:
-
**嵌套
try-catch
**指在一個try
塊或catch
塊內部,再次使用try-catch
結構,以處理不同層次或類型的異常。 -
應用場景:
- 處理復雜操作中不同部分可能引發的多種異常。
- 在特定異常處理邏輯中,需要進一步捕獲和處理異常。
-
示例:
try {// 外層操作try{// 內層操作,可能引發異常int x = int.Parse("invalid");}catch (FormatException ex){Console.WriteLine("內層捕獲: " + ex.Message);throw; // 重新拋出異常給外層處理} } catch (Exception ex) {Console.WriteLine("外層捕獲: " + ex.Message); }// 輸出: // 內層捕獲: Input string was not in a correct format. // 外層捕獲: Input string was not in a correct format.
95. 什么是Exception
類的常用屬性和方法?
答案:
-
常用屬性:
- Message:描述異常的錯誤消息。
- StackTrace:包含調用堆棧信息,便于定位異常發生的位置。
- InnerException:如果異常是由另一個異常引起的,
InnerException
包含原始異常信息。 - Source:引發異常的程序集或應用程序的名稱。
- HelpLink:指向有關異常的幫助文檔的鏈接。
-
常用方法:
- ToString():返回包含異常類型、消息和堆棧跟蹤的完整字符串表示。
- GetBaseException():返回發生異常的原始異常。
- GetType():獲取異常的類型。
-
示例:
try {int x = 10 / 0; // 引發DivideByZeroException } catch (Exception ex) {Console.WriteLine("異常消息: " + ex.Message);Console.WriteLine("堆棧跟蹤: " + ex.StackTrace);Console.WriteLine("內部異常: " + ex.InnerException);Console.WriteLine("異常類型: " + ex.GetType());Console.WriteLine("完整信息: " + ex.ToString()); }// 輸出: // 異常消息: Attempted to divide by zero. // 堆棧跟蹤: at Program.Main(String[] args) in C:\Path\To\Program.cs:line XX // 內部異常: // 異常類型: System.DivideByZeroException // 完整信息: System.DivideByZeroException: Attempted to divide by zero. // at Program.Main(String[] args) in C:\Path\To\Program.cs:line XX
96. 解釋一下try-catch
中的捕獲順序。
答案:
-
捕獲順序指多個
catch
塊按順序檢查和捕獲異常的過程。 -
規則:
- 從上到下:
catch
塊按照從上到下的順序進行匹配,首先匹配到符合的異常類型時,執行對應的catch
塊。 - 從具體到通用:應先捕獲更具體的異常類型,再捕獲更通用的異常類型,避免通用異常塊提前捕獲所有異常,導致具體異常塊無法執行。
- 唯一匹配:每個
catch
塊只能捕獲一次,且同一異常類型在同一try
塊中不能重復捕獲。
- 從上到下:
-
示例:
try {// 引發異常int x = int.Parse("abc"); // FormatException } catch (FormatException ex) {Console.WriteLine("捕獲到FormatException: " + ex.Message); } catch (Exception ex) {Console.WriteLine("捕獲到Exception: " + ex.Message); }
輸出:
捕獲到FormatException: Input string was not in a correct format.
注意:
如果將catch (Exception ex)
放在前面,會導致FormatException
被通用的異常塊捕獲,FormatException
的專用異常塊將無法執行,編譯器會報錯。
97. C#中如何使用finally
塊保證資源釋放?
答案:
-
資源釋放:在
finally
塊中編寫釋放資源的代碼,確保資源在使用完畢后被正確釋放,無論是否發生異常。 -
示例:
FileStream fs = null;try {fs = new FileStream("file.txt", FileMode.Open);// 讀取文件內容 } catch (IOException ex) {Console.WriteLine("IO異常: " + ex.Message); } finally {if (fs != null)fs.Close(); // 確保文件流被關閉 }
-
使用
using
語句簡化:using
語句是一種語法糖,自動在作用域結束時調用Dispose
方法,確保資源釋放。
示例:
try {using (FileStream fs = new FileStream("file.txt", FileMode.Open)){// 讀取文件內容} // 自動調用 fs.Dispose() } catch (IOException ex) {Console.WriteLine("IO異常: " + ex.Message); }
98. 什么是異常過濾器(Exception Filters)?C#如何實現?
答案:
-
**異常過濾器(Exception Filters)**是C# 6.0引入的一種機制,允許在
catch
塊前添加條件,以決定是否捕獲特定異常。它通過when
關鍵字實現。 -
作用:
- 提高異常處理的精確性,只在滿足特定條件時捕獲異常。
- 避免在
catch
塊中嵌套if
語句,提升代碼可讀性。
-
語法:
try {// 可能引發異常的代碼 } catch (Exception ex) when (ex.Message.Contains("specific")) {// 僅當異常消息包含 "specific" 時執行Console.WriteLine("捕獲到特定異常: " + ex.Message); } catch (Exception ex) {// 其他異常處理Console.WriteLine("捕獲到其他異常: " + ex.Message); }
-
示例:
try {int x = int.Parse("invalid"); // 引發FormatException } catch (FormatException ex) when (ex.Message.Contains("Input")) {Console.WriteLine("捕獲到特定的FormatException: " + ex.Message); } catch (FormatException ex) {Console.WriteLine("捕獲到其他FormatException: " + ex.Message); }
輸出:
捕獲到特定的FormatException: Input string was not in a correct format.
99. 如何在C#中實現自定義的異常處理邏輯?
答案:
在C#中實現自定義的異常處理邏輯主要涉及創建自定義異常類,并在適當的地方拋出和捕獲這些異常。自定義異常允許開發者提供更具描述性的錯誤信息,增強代碼的可讀性和可維護性。以下是實現自定義異常處理邏輯的步驟及詳細解析:
步驟一:創建自定義異常類
- 繼承自
Exception
類:自定義異常類通常繼承自System.Exception
,但也可以繼承自更具體的異常類(如ApplicationException
)。 - 添加構造函數:至少實現以下三個構造函數:
- 無參構造函數。
- 接受異常消息的構造函數。
- 接受異常消息和內部異常的構造函數(用于異常鏈)。
- 實現序列化構造函數(可選):如果需要跨應用域傳遞異常,需實現序列化構造函數。
示例:
using System;
using System.Runtime.Serialization;[Serializable]
public class InvalidAgeException : Exception
{public InvalidAgeException(){}public InvalidAgeException(string message): base(message){}public InvalidAgeException(string message, Exception inner): base(message, inner){}protected InvalidAgeException(SerializationInfo info, StreamingContext context): base(info, context){}
}
步驟二:在適當的位置拋出自定義異常
在業務邏輯中,當檢測到特定的錯誤條件時,拋出自定義異常以提供更具語義化的錯誤信息。
示例:
public class Person
{private int age;public int Age{get { return age; }set{if (value < 0 || value > 120){throw new InvalidAgeException("年齡必須在0到120之間。");}age = value;}}
}
步驟三:捕獲和處理自定義異常
在調用代碼中,通過try-catch
結構捕獲自定義異常,并進行相應的處理。
示例:
class Program
{static void Main(string[] args){Person person = new Person();try{person.Age = -5; // 引發InvalidAgeException}catch (InvalidAgeException ex){Console.WriteLine("捕獲到自定義異常: " + ex.Message);// 可以執行額外的處理,如記錄日志、提示用戶等}catch (Exception ex){Console.WriteLine("捕獲到其他異常: " + ex.Message);}}// 輸出:// 捕獲到自定義異常: 年齡必須在0到120之間。
}
高級應用:異常鏈(Exception Chaining)
通過在新異常中包含內部異常,可以保留異常的原始上下文,便利問題的追蹤和調試。
示例:
public void ProcessData(string input)
{try{int age = int.Parse(input); // 可能引發FormatException// 其他處理邏輯}catch (FormatException ex){throw new InvalidAgeException("輸入的年齡格式不正確。", ex);}
}class Program
{static void Main(string[] args){try{ProcessData("invalid_number");}catch (InvalidAgeException ex){Console.WriteLine("捕獲到自定義異常: " + ex.Message);Console.WriteLine("內部異常: " + ex.InnerException.Message);}}// 輸出:// 捕獲到自定義異常: 輸入的年齡格式不正確。// 內部異常: Input string was not in a correct format.
}
注意事項:
- 保持異常類的命名清晰:異常類應清晰表達錯誤的類型和原因,例如
InvalidAgeException
。 - 避免使用過多的自定義異常:僅在有利于提升代碼可讀性和可維護性的情況下使用自定義異常。
- 確保自定義異常具備序列化能力:如果異常需要跨應用域傳遞,應確保異常類可序列化。
100. 如何在C#中有效地記錄和追蹤異常信息?
答案:
有效的異常記錄和追蹤對于提高應用程序的穩定性和可維護性至關重要。以下是C#中實現有效異常記錄和追蹤的一些方法:
1. 使用日志框架
選擇合適的日志框架(如NLog
、log4net
、Serilog
等),并根據項目需求進行配置。日志框架提供豐富的功能,如不同級別的日志記錄、多目標輸出、格式化等。
示例(使用Serilog):
using Serilog;class Program
{static void Main(string[] args){// 配置SerilogLog.Logger = new LoggerConfiguration().WriteTo.Console().WriteTo.File("logs\\app.log", rollingInterval: RollingInterval.Day).CreateLogger();try{// 執行操作,可能引發異常int x = int.Parse("invalid");}catch (Exception ex){// 記錄異常Log.Error(ex, "發生了一個錯誤");}finally{// 關閉并刷新日志Log.CloseAndFlush();}}// 輸出到控制臺和文件:// [Error] 發生了一個錯誤// Exception details...
}
2. 使用異常過濾器
通過when
關鍵字,在catch
塊中設置條件,記錄特定異常的信息或采取特定的行動。
示例:
try
{// 可能引發異常的代碼
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{Log.Error(ex, "數據格式錯誤");
}
3. 捕獲堆棧跟蹤信息
使用ex.StackTrace
屬性,記錄引發異常的具體位置和調用路徑,幫助快速定位問題。
示例:
catch (Exception ex)
{Log.Error($"異常信息: {ex.Message}, 堆棧跟蹤: {ex.StackTrace}");
}
4. 使用全局異常處理
對于跨界面或跨應用域的應用(如WPF、WinForms、ASP.NET),使用全局異常處理機制,捕獲未處理的異常,并記錄日志。
ASP.NET Core 示例:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{app.UseExceptionHandler(errorApp =>{errorApp.Run(async context =>{context.Response.StatusCode = 500;context.Response.ContentType = "text/plain";var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();if (exceptionHandlerPathFeature?.Error != null){Log.Error(exceptionHandlerPathFeature.Error, "未處理的異常");await context.Response.WriteAsync("服務器內部錯誤");}});});// 其他中間件
}
5. 定制異常信息
在自定義異常類中添加額外的信息,如錯誤代碼、相關數據字段,有助于更細粒度的異常處理和日志記錄。
示例:
[Serializable]
public class ValidationException : Exception
{public int ErrorCode { get; set; }public string FieldName { get; set; }public ValidationException(string message, int errorCode, string fieldName): base(message){ErrorCode = errorCode;FieldName = fieldName;}// 其他構造函數
}// 使用
try
{// 可能引發驗證異常的操作throw new ValidationException("輸入無效", 1001, "Age");
}
catch (ValidationException ex)
{Log.Warn($"驗證錯誤 - Field: {ex.FieldName}, Code: {ex.ErrorCode}, Message: {ex.Message}");
}
6. 監控和報警
集成監控工具(如ELK stack、Azure Application Insights、Sentry)來實時監控異常,設置報警機制,及時響應潛在的問題。
示例(使用Application Insights):
// 在Startup.cs中配置
public void ConfigureServices(IServiceCollection services)
{services.AddApplicationInsightsTelemetry(Configuration["ApplicationInsights:InstrumentationKey"]);
}// 在catch塊中記錄異常
catch (Exception ex)
{TelemetryClient telemetry = new TelemetryClient();telemetry.TrackException(ex);
}
總結
通過有效的異常記錄和追蹤,可以大幅提升應用程序的穩定性、可維護性和可調試性。結合合適的日志框架、全局異常處理機制和監控工具,開發者可以系統化地管理和響應異常,確保應用程序的健壯性和可靠性。
以上為100道C#高頻經典面試題的解析答案,希望這些內容能幫助您更好地理解和掌握C#相關的知識,為面試做好充分的準備!
需要深入學習C#開發技術的同學可以訂閱C#開發從入門到精通系列專欄學習
https://blog.csdn.net/martian665/category_8983778.html,
創作不易,希望大家能夠關注、收藏和點贊支持一下哦。