序列化與反序列化
1. 什么是序列化和反序列化?用途是什么?
// 序列化示例
Person person = new Person { Name = "Alice", Age = 30 };
string json = JsonSerializer.Serialize(person); // 序列化為JSON// 反序列化示例
Person deserialized = JsonSerializer.Deserialize<Person>(json);
- 核心概念:
- 序列化:將對象狀態轉換為可存儲/傳輸格式(如JSON、XML、二進制)
- 反序列化:將數據流還原為對象
- 關鍵用途:
- 網絡傳輸(API通信)
- 數據持久化(保存到文件/數據庫)
- 進程間通信
- 緩存機制
2. JSON vs XML 序列化的優缺點
特性 | JSON | XML |
---|---|---|
可讀性 | 高(輕量級) | 中(標簽冗余) |
數據體積 | 小(無標簽) | 大(標簽占40%+空間) |
解析性能 | 快(比XML快2-10倍) | 慢(DOM解析復雜) |
類型安全 | 弱(無schema) | 強(XSD支持) |
二進制支持 | Base64編碼 | 原生支持(CDATA) |
適用場景 | Web API、移動應用 | 企業系統、SOAP服務 |
3. 循環引用處理
// Newtonsoft.Json解決方案
var settings = new JsonSerializerSettings {ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(obj, settings);// System.Text.Json解決方案
options.ReferenceHandler = ReferenceHandler.Preserve;
- 根本原因:對象相互引用形成閉環(如
Parent.Child
和Child.Parent
) - 解決方案:
- 忽略循環引用(
ReferenceLoopHandling.Ignore
) - 使用
[JsonIgnore]
標記屬性 - DTO模式(僅序列化必要數據)
- 自定義ID追蹤(
PreserveReferencesHandling
)
- 忽略循環引用(
4. 自定義序列化
// 實現ISerializable接口
[Serializable]
public class CustomObject : ISerializable
{public void GetObjectData(SerializationInfo info, StreamingContext context){info.AddValue("customField", _value);}protected CustomObject(SerializationInfo info, StreamingContext context){_value = info.GetString("customField");}
}// JSON自定義轉換器
public class DateTimeConverter : JsonConverter<DateTime>
{public override DateTime Read(ref Utf8JsonReader reader, ...) => DateTime.Parse(reader.GetString());public override void Write(Utf8JsonWriter writer, DateTime value, ...)=> writer.WriteStringValue(value.ToString("yyyy-MM-dd"));
}
5. 版本控制策略
- 向前兼容:
- 使用
[OptionalField]
特性 - 反序列化回調(
OnDeserializing
)
- 使用
- 向后兼容:
- 避免刪除已序列化字段
- 新字段添加默認值
- 最佳實踐:
- 語義版本控制(SemVer)
- 自定義版本字段
- 使用協議緩沖區(protobuf)等版本容忍格式
泛型
1. 泛型核心優勢
// 類型安全集合
List<string> strings = new List<string>();
strings.Add("text"); // 編譯時類型檢查
// strings.Add(42); // 編譯錯誤// 避免裝箱
List<int> numbers = new List<int>(); // 無裝箱開銷
- 三大優勢:
- 編譯時類型檢查
- 消除裝箱/拆箱(值類型性能提升20-50%)
- 代碼復用(同一算法處理不同類型)
2. 泛型約束類型
public class Processor<T> where T : IComparable, // 接口約束new(), // 構造函數約束struct // 值類型約束
{public void Sort(T[] data) { /*...*/ }
}
約束類型 | 語法 | 應用場景 |
---|---|---|
接口約束 | where T : IDisposable | 確保類型實現特定行為 |
基類約束 | where T : Stream | 限制繼承層次 |
構造函數約束 | where T : new() | 創建實例(new T() ) |
值類型/引用類型約束 | where T : struct | 優化值類型操作 |
非托管約束 | where T : unmanaged | 不安全代碼/指針操作 |
3. 協變(in)與逆變(out)
// 協變:子類→父類 (out)
IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings; // 安全// 逆變:父類→子類 (in)
Action<object> logObject = obj => Console.Write(obj);
Action<string> logString = logObject; // 安全
logString("text"); // 實際執行logObject
- 核心規則:
- 協變(
out
):返回值類型,支持更具體的子類型 - 逆變(
in
):參數類型,支持更通用的父類型 - 只適用于接口和委托
- 協變(
4. 泛型與反射交互
// 創建泛型類型實例
Type openType = typeof(Dictionary<,>);
Type closedType = openType.MakeGenericType(typeof(int), typeof(string));
object dict = Activator.CreateInstance(closedType);// 調用泛型方法
MethodInfo method = typeof(Serializer).GetMethod("Serialize");
MethodInfo closedMethod = method.MakeGenericMethod(typeof(Person));
closedMethod.Invoke(null, new object[] { person });
5. 泛型接口設計模式
public interface IRepository<TEntity, TKey> where TEntity : IEntity<TKey>
{TEntity GetById(TKey id);void Add(TEntity entity);
}public class UserRepository : IRepository<User, int>
{public User GetById(int id) { /* DB查詢 */ }public void Add(User entity) { /* DB插入 */ }
}
集合
1. List vs Array
// Array - 固定大小
int[] array = new int[10];
array[0] = 1; // 賦值
// array[10] = 1; // 運行時越界錯誤// List<T> - 動態擴展
List<int> list = new List<int>(capacity: 100);
list.Add(1); // 自動擴容
list.RemoveAt(0); // 動態收縮
特性 | Array | List |
---|---|---|
大小 | 固定 | 動態擴展 |
內存分配 | 連續塊 | 動態數組+緩沖池 |
插入/刪除性能 | O(n) | O(n)(尾部O(1)) |
隨機訪問 | O(1) | O(1) |
線程安全 | 否 | 否 |
2. Dictionary工作原理
var dict = new Dictionary<string, int>();
dict.Add("key1", 42); // 內部實現偽代碼:
// 1. 計算哈希:key.GetHashCode() -> bucketIndex
// 2. 處理沖突:鏈地址法(鏈表存儲沖突項)
// 3. 存儲鍵值對:entries[bucketIndex] = new Entry(key, value)
- 哈希沖突解決方案:
- 開放尋址法(.NET Core+)
- 鏈地址法(.NET Framework)
- 時間復雜度:
- 查找/插入/刪除:平均O(1),最壞O(n)
- 最佳實踐:
- 實現
GetHashCode()
和Equals()
的規范重寫 - 設置合理初始容量減少擴容
- 實現
3. 集合接口層次
- 核心接口:
IEnumerable<T>
:基本迭代(GetEnumerator
)ICollection<T>
:添加/刪除(Count
,Add
,Remove
)IList<T>
:索引訪問(this[index]
,Insert
)ISet<T>
:集合操作(UnionWith
,IntersectWith
)
4. 自定義集合實現
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged
{protected override void InsertItem(int index, T item){base.InsertItem(index, item);OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);}protected virtual void OnCollectionChanged(...) => CollectionChanged?.Invoke(this, args);
}
- 繼承
Collection<T>
基類 - 重寫關鍵方法:
InsertItem
,SetItem
,RemoveItem
- 實現通知機制(如
INotifyCollectionChanged
)
5. 線程安全集合
// 并發字典
var concurrentDict = new ConcurrentDictionary<string, int>();
concurrentDict.TryAdd("key", 42);// 生產者-消費者隊列
BlockingCollection<int> queue = new BlockingCollection<int>();
// 生產者
queue.Add(1);
// 消費者
int item = queue.Take();
集合類型 | 適用場景 |
---|---|
ConcurrentDictionary | 高頻讀/寫的鍵值存儲 |
ConcurrentQueue | 任務隊列(生產者-消費者) |
ConcurrentBag | 無序對象池 |
BlockingCollection | 有界/無界阻塞隊列 |
Lambda表達式
1. Lambda vs 匿名方法
// 匿名方法(C# 2.0)
Func<int, int> square = delegate(int x) { return x * x; };// Lambda表達式(C# 3.0+)
Func<int, int> square = x => x * x;
特性 | 匿名方法 | Lambda表達式 |
---|---|---|
語法簡潔性 | 冗長 | 簡潔 |
參數類型推斷 | 需顯式聲明 | 可推斷 |
表達式樹支持 | 不支持 | 支持 |
閉包支持 | 支持 | 支持 |
單行返回值 | 需return語句 | 自動返回 |
2. 閉包與變量捕獲
int factor = 2;
Func<int, int> multiplier = x => x * factor;
Console.WriteLine(multiplier(5)); // 10// 修改捕獲變量
factor = 3;
Console.WriteLine(multiplier(5)); // 15
- 閉包原理:
- 編譯器生成隱藏類(closure類)
- 捕獲變量轉為類的字段
- Lambda成為實例方法
- 陷阱:
- 循環變量捕獲問題(所有迭代共享同一變量)
- 解決方案:循環內聲明臨時變量
3. 表達式樹(Expression Trees)
Expression<Func<int, bool>> expr = x => x > 5 && x < 10;// 解析表達式樹
BinaryExpression body = (BinaryExpression)expr.Body;
ParameterExpression param = (ParameterExpression)expr.Parameters[0];
- 核心特性:
- 將代碼轉為可遍歷的數據結構
- LINQ to SQL等ORM的核心技術
- 應用場景:
- 動態SQL生成
- 運行時條件組合
- 自定義查詢翻譯
4. 變量作用域規則
int outer = 10;
Action action = () =>
{int inner = 20;Console.WriteLine(outer + inner); // 訪問外部變量
};
// Console.WriteLine(inner); // 錯誤:inner不可訪問
- 作用域層次:
- Lambda參數
- Lambda內部聲明
- 外部方法變量
- 類成員變量
- 生命周期:
- 捕獲變量的生命周期延至所有委托被GC回收
5. Lambda性能優化
// 避免:高頻調用中重復編譯表達式樹
Expression<Func<Person, bool>> expr = p => p.Age > 18;
Func<Person, bool> compiled = expr.Compile(); // 緩存此委托// 優先使用靜態Lambda(C# 9.0+)
Func<int, int> square = static x => x * x; // 不捕獲上下文
- 優化策略:
- 緩存編譯后的委托
- 避免在高頻循環中捕獲變量
- 使用靜態Lambda減少內存分配
- 避免深層嵌套的Lambda
LINQ
1. 延遲執行 vs 立即執行
// 延遲執行(未真正執行)
IEnumerable<int> query = data.Where(x => x > 5);// 觸發執行的操作:
var list = query.ToList(); // 立即執行
int count = query.Count(); // 立即執行
var first = query.First(); // 立即執行// 流式執行(yield return)
foreach (var item in query) // 遍歷時逐步執行
{// 每次迭代處理一個元素
}
2. Select vs SelectMany
// Select:一對一轉換
var names = persons.Select(p => p.Name);// SelectMany:集合展開
var allPhones = persons.SelectMany(p => p.Phones);// 模擬SQL JOIN
var orders = customers.SelectMany(c => c.Orders,(customer, order) => new { customer.Name, order.Id }
);
3. LINQ性能優化
// 數據庫查詢優化
var results = context.Users.Where(u => u.Age > 18) // 在數據庫執行.AsNoTracking() // 禁用變更追蹤.Select(u => new { u.Name }) // 僅選擇必要字段.ToList();// 內存集合優化
var local = data.Where(x => x.IsActive).OrderBy(x => x.Name) // 先過濾再排序.Take(20) // 盡早限制數量.ToList();
- 關鍵優化點:
- 數據庫:使用投影減少數據傳輸
- EF Core:
AsNoTracking()
用于只讀查詢 - 內存:避免N+1查詢模式
- 使用
Any()
替代Count() > 0
4. IQueryable vs IEnumerable
// IEnumerable - 客戶端執行
var result = context.Users.AsEnumerable() // 切換為客戶端評估.Where(u => ComplexLogic(u)); // 在內存中執行// IQueryable - 數據庫執行
var result = context.Users.Where(u => u.Age > 18) // 轉換為SQL.ToList(); // 在數據庫執行
特性 | IEnumerable | IQueryable |
---|---|---|
執行位置 | 客戶端內存 | 數據源(如數據庫) |
查詢能力 | LINQ to Objects | 支持提供程序翻譯(如SQL) |
延遲執行 | 支持 | 支持 |
性能特點 | 適合內存數據集 | 減少數據傳輸量 |
5. 自定義LINQ提供程序
public class CustomQueryable<T> : IQueryable<T>
{public Type ElementType => typeof(T);public Expression Expression { get; }public IQueryProvider Provider { get; }// 實現GetEnumerator和必要接口
}public class CustomProvider : IQueryProvider
{public IQueryable CreateQuery(Expression expression) { ... }public object Execute(Expression expression){// 解析表達式樹var translator = new CustomTranslator();translator.Visit(expression);// 執行自定義查詢邏輯return ExecuteDatabaseQuery(translator.GetQuery());}
}
- 實現步驟:
- 實現
IQueryable
和IQueryProvider
- 解析表達式樹(
ExpressionVisitor
) - 轉換為目標查詢語言(如SQL)
- 執行查詢并映射結果
- 實現
- 難點:
- 表達式樹的完整解析
- 參數化查詢處理
- 嵌套查詢支持
綜合問題深度分析
1. 泛型+集合+LINQ綜合應用
public class DataService<T> where T : class
{public List<T> FilterAndSort(List<T> data, Func<T, bool> filter,Func<T, object> sorter){return data.Where(filter).OrderBy(sorter).ToList();}
}// 使用
var userService = new DataService<User>();
var activeUsers = userService.FilterAndSort(users, u => u.IsActive, u => u.LastLoginDate
);
2. 多格式序列化服務
public interface ISerializer<T>
{string Serialize(T obj);T Deserialize(string data);
}public class JsonSerializer<T> : ISerializer<T> { /*...*/ }
public class XmlSerializer<T> : ISerializer<T> { /*...*/ }public class SerializationService<T>
{private readonly ISerializer<T> _serializer;public SerializationService(ISerializer<T> serializer) => _serializer = serializer;public string ToString(T obj) => _serializer.Serialize(obj);public T FromString(string data) => _serializer.Deserialize(data);
}
3. 表達式樹工作原理
4. 大型數據集處理策略
// 流式JSON反序列化
await using var stream = File.OpenRead("large.json");
var data = JsonSerializer.DeserializeAsyncEnumerable<Person>(stream);await foreach (var person in data)
{// 單條處理,內存占用恒定
}// 分塊處理
const int chunkSize = 1000;
for (int i = 0; i < data.Count; i += chunkSize)
{var chunk = data.Skip(i).Take(chunkSize);ProcessChunk(chunk);
}
5. Lambda實現策略模式
public class PaymentProcessor
{private Func<Order, PaymentResult> _paymentStrategy;public void SetPaymentStrategy(Func<Order, PaymentResult> strategy) => _paymentStrategy = strategy;public PaymentResult ProcessOrder(Order order)=> _paymentStrategy?.Invoke(order) ?? throw new InvalidOperationException();
}// 使用
var processor = new PaymentProcessor();
processor.SetPaymentStrategy(order => order.Total > 1000 ? ProcessCreditCard(order) : ProcessCash(order));