公司动态
C#进阶核心:面向对象设计、异常处理、LINQ与异步编程实战指南
如果你已经掌握了C#的基础语法能够写出简单的控制台程序甚至完成了一些小项目那么恭喜你你已经成功迈出了第一步。但接下来你可能会陷入一个典型的“中级开发者陷阱”感觉什么都会一点但面对一个稍复杂的真实项目比如开发一个带用户管理、数据持久化和API接口的桌面应用却不知从何下手代码写着写着就变得混乱不堪。这种感觉就像你学会了所有乐器的演奏方法却不知道如何将它们编排成一首和谐的乐章。从“会写代码”到“会写好代码”从“实现功能”到“构建可维护、可扩展的应用程序”这中间的鸿沟正是“进阶”所要跨越的核心。本文是《Learn Complete C# – Beginner to Advanced》系列的第二部分我们将不再讨论变量、循环这些基础语法。我们将直击痛点深入C#进阶开发中那些真正决定代码质量、开发效率和项目成败的核心领域面向对象设计的精髓、异常处理的正确姿势、集合与LINQ的高效运用、以及异步编程如何避免让程序“卡死”。这些知识是把你从一个“代码搬运工”提升为“软件设计师”的关键。读完本文你将能清晰地构建中小型应用的骨架优雅地处理程序中的各种意外用更少的代码做更多的事并写出响应迅速的程序。我们不仅讲“是什么”更重点剖析“为什么”和“怎么用”并附带大量可直接用于项目的代码示例。1. 面向对象设计超越“类与对象”的语法层面很多教程讲到面向对象就是“类、对象、继承、封装、多态”这十二个字。这没错但这是语法概念。进阶的关键在于设计思想如何用这些语法构建出灵活、健壮、易于变化的软件结构。1.1 封装不仅仅是private字段封装的核心是“信息隐藏”和“职责明确”。一个设计良好的类应该像一个黑盒对外提供清晰、稳定的接口方法而将复杂的实现细节和易变的数据隐藏在内部。反面教材一个“透明”的订单类// Bad Practice: 过度暴露内部细节 public class Order { public ListOrderItem Items; // 公共字段外部可以随意修改集合 public decimal TotalAmount; // 计算总额的责任被抛给了外部 public Customer Customer; // 直接暴露关联对象 }这段代码的问题在于外部代码可以直接order.Items.Add(...)或order.Items.Clear()破坏了订单项管理的逻辑完整性。TotalAmount需要外部计算和维护容易产生不一致。耦合度高Order的内部结构变化会直接影响所有使用它的代码。正面示例具有良好封装的订单类// Good Practice: 隐藏细节提供明确接口 public class Order { private readonly ListOrderItem _items new ListOrderItem(); private readonly Customer _customer; // 通过构造函数注入依赖确保订单创建时就有客户 public Order(Customer customer) { _customer customer ?? throw new ArgumentNullException(nameof(customer)); CreatedTime DateTime.UtcNow; } public DateTime CreatedTime { get; private set; } // 创建时间不应被修改 public IReadOnlyCollectionOrderItem Items _items.AsReadOnly(); // 只读视图 public Customer Customer _customer; // 只读属性 // 核心业务逻辑封装在类内部 public decimal CalculateTotalAmount() { return _items.Sum(item item.UnitPrice * item.Quantity); } public void AddItem(Product product, int quantity) { if (product null) throw new ArgumentNullException(nameof(product)); if (quantity 0) throw new ArgumentException(Quantity must be positive., nameof(quantity)); // 这里可以加入业务规则例如检查库存、限购等 var existingItem _items.FirstOrDefault(i i.ProductId product.Id); if (existingItem ! null) { existingItem.IncreaseQuantity(quantity); } else { _items.Add(new OrderItem(product, quantity)); } } // 标记订单状态等业务操作 public void MarkAsPaid() { // 检查状态转换的合法性如从未支付到已支付 // 触发领域事件等 } }设计要点分析不变性Immutability_customer和CreatedTime在构造后不可变保证了对象的部分状态始终一致。控制修改途径Items属性返回一个只读集合防止外部直接修改集合。添加物品必须通过AddItem方法该方法内部可以执行所有必要的业务规则校验。计算属性CalculateTotalAmount方法保证了总额总是根据当前订单项实时计算不存在数据不一致。明确的职责Order类负责管理订单项和计算逻辑外部代码无需关心细节。1.2 继承与多态谨慎使用“是一个is-a”关系继承是代码复用的强大工具但滥用继承尤其是深度继承会导致代码僵化、脆弱。“组合优于继承”是更常被倡导的原则。场景我们有一个报表系统需要生成PDF和Excel格式的报表。不灵活的继承设计public abstract class ReportGenerator { public abstract void GenerateHeader(); public abstract void GenerateBody(); public abstract void GenerateFooter(); public void GenerateReport() { GenerateHeader(); GenerateBody(); GenerateFooter(); } } public class PdfReportGenerator : ReportGenerator { public override void GenerateHeader() { /* PDF特有的页眉逻辑 */ } public override void GenerateBody() { /* PDF特有的正文逻辑 */ } public override void GenerateFooter() { /* PDF特有的页脚逻辑 */ } } public class ExcelReportGenerator : ReportGenerator { public override void GenerateHeader() { /* Excel特有的页眉逻辑 */ } public override void GenerateBody() { /* Excel特有的正文逻辑 */ } public override void GenerateFooter() { /* Excel特有的页脚逻辑 */ } }问题如果现在要增加一个CSV报表或者报表的头部、正文、尾部逻辑需要复用比如PDF和Word的页眉类似这个继承体系就很难扩展。更灵活的组合与策略模式设计// 定义策略接口 public interface IReportPartGenerator { void Generate(ReportData data); } public class PdfHeaderGenerator : IReportPartGenerator { /* ... */ } public class ExcelHeaderGenerator : IReportPartGenerator { /* ... */ } public class CommonBodyGenerator : IReportPartGenerator { /* ... */ } // 通用的正文生成器 public class PdfFooterGenerator : IReportPartGenerator { /* ... */ } public class ExcelFooterGenerator : IReportPartGenerator { /* ... */ } // 报告生成器上下文组合不同的部分生成器 public class ReportGenerator { private readonly IReportPartGenerator _headerGenerator; private readonly IReportPartGenerator _bodyGenerator; private readonly IReportPartGenerator _footerGenerator; public ReportGenerator(IReportPartGenerator headerGenerator, IReportPartGenerator bodyGenerator, IReportPartGenerator footerGenerator) { _headerGenerator headerGenerator; _bodyGenerator bodyGenerator; _footerGenerator footerGenerator; } public void GenerateReport(ReportData data) { _headerGenerator.Generate(data); _bodyGenerator.Generate(data); _footerGenerator.Generate(data); } } // 使用依赖注入等方式灵活组装 var pdfReportGenerator new ReportGenerator( new PdfHeaderGenerator(), new CommonBodyGenerator(), // 复用了通用逻辑 new PdfFooterGenerator() );设计优势高内聚、低耦合每个生成器只负责一个具体的部分职责单一。易于扩展要新增一种报表格式如CSV只需实现对应的IReportPartGenerator无需修改现有类。易于复用CommonBodyGenerator可以被PDF和Excel报告共用。易于测试每个部分都可以独立进行单元测试。多态的真谛多态的魅力不在于继承树有多深而在于客户端代码依赖于抽象接口而非具体实现。上面的ReportGenerator依赖于IReportPartGenerator接口至于运行时传入的是PDF还是Excel的实现它并不关心这就是多态。2. 异常处理从“抓了又抛”到构建健壮性try-catch人人都会写但如何有效处理异常却是区分新手和老手的重要标志。糟糕的异常处理会隐藏错误让调试变得噩梦般困难。2.1 异常处理的核心原则只捕获你能处理的异常不要用catch (Exception ex)捕获所有异常然后什么都不做空的catch块或只是记录一下。如果你不知道如何处理这个异常就应该让它向上层抛出。使用具体的异常类型优先捕获FileNotFoundException、ArgumentNullException等具体异常而不是通用的Exception。异常信息要丰富抛出自定义异常时务必提供清晰易懂的Message并考虑传递内部异常InnerException。区分业务异常和系统异常像“用户余额不足”、“商品已下架”这类属于可预见的业务规则违规应使用自定义的业务异常如InsufficientBalanceException而不是用系统异常如InvalidOperationException来代表。2.2 实践创建有意义的自定义异常// 自定义业务异常 public class DomainException : Exception { // 可以添加错误码等业务相关属性 public string ErrorCode { get; } public DomainException(string message, string errorCode null) : base(message) { ErrorCode errorCode; } public DomainException(string message, Exception innerException, string errorCode null) : base(message, innerException) { ErrorCode errorCode; } } public class InsufficientBalanceException : DomainException { public decimal CurrentBalance { get; } public decimal RequiredAmount { get; } public InsufficientBalanceException(decimal currentBalance, decimal requiredAmount) : base($Insufficient balance. Current: {currentBalance:C}, Required: {requiredAmount:C}, INSUFFICIENT_BALANCE) { CurrentBalance currentBalance; RequiredAmount requiredAmount; } } // 使用示例 public class PaymentService { public void ProcessPayment(string userId, decimal amount) { var user _userRepository.GetById(userId); if (user.Balance amount) { // 抛出具体的业务异常上层如控制器可以捕获并转换为对用户友好的错误信息 throw new InsufficientBalanceException(user.Balance, amount); } try { // 调用第三方支付网关 _paymentGateway.Charge(amount, user.PaymentToken); user.DeductBalance(amount); _userRepository.Update(user); } catch (PaymentGatewayException ex) // 捕获特定的第三方异常 { // 记录日志并可能包装成更通用的业务异常向上抛 _logger.LogError(ex, Payment gateway failed for user {UserId}, userId); throw new DomainException(Payment processing failed, please try again later., ex, PAYMENT_FAILED); } // 不捕获其他未知异常让它们冒泡到全局异常处理器 } }2.3 全局异常处理ASP.NET Core Web API 示例在Web API中你通常不希望任何未处理的异常直接返回给客户端。一个全局异常中间件可以优雅地处理这个问题。// 自定义中间件 public class ExceptionHandlingMiddleware { private readonly RequestDelegate _next; private readonly ILoggerExceptionHandlingMiddleware _logger; public ExceptionHandlingMiddleware(RequestDelegate next, ILoggerExceptionHandlingMiddleware logger) { _next next; _logger logger; } public async Task InvokeAsync(HttpContext context) { try { await _next(context); } catch (DomainException ex) // 捕获业务异常 { _logger.LogWarning(ex, A domain exception occurred.); context.Response.StatusCode StatusCodes.Status400BadRequest; // 或自定义状态码 await context.Response.WriteAsJsonAsync(new { errorCode ex.ErrorCode, message ex.Message, // 对于InsufficientBalanceException可以返回更详细的数据 currentBalance (ex as InsufficientBalanceException)?.CurrentBalance, requiredAmount (ex as InsufficientBalanceException)?.RequiredAmount }); } catch (Exception ex) // 捕获其他所有未预料异常 { _logger.LogError(ex, An unhandled exception occurred.); // 生产环境不应返回详细错误给客户端 context.Response.StatusCode StatusCodes.Status500InternalServerError; await context.Response.WriteAsJsonAsync(new { errorCode INTERNAL_SERVER_ERROR, message An internal server error has occurred. }); } } } // 在 Program.cs 或 Startup.cs 中注册 app.UseMiddlewareExceptionHandlingMiddleware();3. 集合与LINQ告别循环声明式编程C#的LINQLanguage Integrated Query是处理集合数据的革命性特性。它让你能用类似SQL的声明式语法来操作内存中的数据代码更简洁意图更清晰。3.1 常用集合类型的选择ListT最通用的动态数组。随机访问快O(1)但在中间插入/删除慢O(n)。适用于大多数需要有序、可重复、频繁按索引访问的场景。DictionaryTKey, TValue基于哈希表的键值对集合。按键查找、插入、删除都非常快平均O(1)。适用于需要通过唯一键快速查找数据的场景。HashSetT不包含重复元素的集合基于哈希表。判断是否包含某个元素极快O(1)。适用于需要去重或快速存在性检查的场景。QueueT/StackT先进先出FIFO/后进先出LIFO的集合。适用于任务队列、撤销操作等特定算法。IEnumerableT所有集合的基接口代表一个可枚举的序列。LINQ操作主要针对IEnumerableT。3.2 LINQ核心操作查询语法 vs 方法语法LINQ有两种写法查询语法类似SQL和方法语法链式调用。方法语法更灵活也是更主流和推荐的方式。public class Product { public int Id { get; set; } public string Name { get; set; } public string Category { get; set; } public decimal Price { get; set; } public int Stock { get; set; } } ListProduct products GetProducts(); // 假设这个方法返回产品列表 // 目标找出“电子产品”类别中库存大于0并按价格降序排列的前5个产品名和价格。 // 方法1命令式编程传统循环 - 冗长意图分散 var resultTraditional new List(string Name, decimal Price)(); foreach (var p in products) { if (p.Category Electronics p.Stock 0) { resultTraditional.Add((p.Name, p.Price)); } } resultTraditional.Sort((a, b) b.Price.CompareTo(a.Price)); var top5Traditional resultTraditional.Take(5).ToList(); // 方法2LINQ查询语法 - 清晰但某些操作受限 var resultQuerySyntax (from p in products where p.Category Electronics p.Stock 0 orderby p.Price descending select new { p.Name, p.Price }).Take(5).ToList(); // 方法3LINQ方法语法推荐- 灵活功能强大可读性高 var resultMethodSyntax products .Where(p p.Category Electronics p.Stock 0) // 过滤 .OrderByDescending(p p.Price) // 排序 .Select(p new { p.Name, p.Price }) // 投影选择需要的字段 .Take(5) // 取前5个 .ToList(); // 立即执行将结果物化为列表显然LINQ方法语法最简洁且清晰地表达了“过滤 - 排序 - 投影 - 限制 - 物化”这一系列操作。3.3 关键概念延迟执行 vs 立即执行这是LINQ最重要的概念之一理解错误会导致性能问题。延迟执行Deferred Execution像Where,Select,OrderBy这样的操作它们并不立即执行查询而是返回一个“查询计划”一个IEnumerableT。真正的查询执行被推迟到当你迭代这个序列时例如使用foreach或调用ToList,ToArray,First,Count等方法。立即执行Immediate Execution像ToList(),ToArray(),First(),Count(),Max()这样的操作会立即触发查询执行并返回具体的结果列表、数组、单个值等。// 示例延迟执行的陷阱 var expensiveQuery products.Where(p ExpensiveFilter(p)); // 1. 定义查询未执行 Console.WriteLine(Query defined.); foreach (var item in expensiveQuery) // 2. 第一次迭代执行过滤 { Console.WriteLine(item.Name); } // 假设 ExpensiveFilter 是一个耗时操作这里会执行 N 次Nproducts.Count foreach (var item in expensiveQuery) // 3. 第二次迭代再次执行过滤 { Console.WriteLine(item.Name); } // 又执行了 N 次如果数据源是数据库这就是两次独立的查询。 // 正确做法对需要重用的结果进行物化 var filteredList products.Where(p ExpensiveFilter(p)).ToList(); // 立即执行一次结果存入内存 Console.WriteLine(Query executed and materialized.); foreach (var item in filteredList) // 遍历内存列表很快 { Console.WriteLine(item.Name); } foreach (var item in filteredList) // 再次遍历还是很快 { Console.WriteLine(item.Name); }3.4 进阶LINQ操作GroupBy, Join, Aggregate// 1. GroupBy: 按类别分组并计算每类的平均价格和总库存 var categoryStats products .GroupBy(p p.Category) .Select(g new { Category g.Key, AveragePrice g.Average(p p.Price), TotalStock g.Sum(p p.Stock), Products g.ToList() // 如果需要保留组内所有产品 }) .ToList(); // 2. Join: 假设有另一个订单项列表关联产品和订单项 ListOrderItem orderItems GetOrderItems(); var productSales products .Join(orderItems, p p.Id, // 产品表关联键 oi oi.ProductId, // 订单项表关联键 (p, oi) new { Product p, OrderItem oi }) // 结果选择器 .GroupBy(x x.Product.Id) .Select(g new { ProductId g.Key, ProductName g.First().Product.Name, TotalQuantitySold g.Sum(x x.OrderItem.Quantity), TotalRevenue g.Sum(x x.OrderItem.Quantity * x.OrderItem.UnitPrice) }) .OrderByDescending(x x.TotalRevenue) .ToList(); // 3. Aggregate: 复杂的聚合操作例如计算所有产品的总价值价格*库存 decimal totalInventoryValue products .Aggregate(0M, // 种子值初始累加器 (sum, product) sum (product.Price * product.Stock)); // 累加函数 // 等同于products.Sum(p p.Price * p.Stock)但 Aggregate 更通用。4. 异步编程释放被阻塞的线程在桌面应用或Web服务器中同步执行耗时操作如文件I/O、网络请求、数据库查询会阻塞当前线程导致界面“卡死”或服务器无法响应新请求。异步编程async/await就是为了解决这个问题。4.1 核心概念async, await, Taskasync修饰方法表明该方法内部包含异步操作。它本身不会让方法异步运行只是允许方法体内使用await。await用于等待一个Task或TaskT完成。当执行到await时当前方法会“暂停”控制权返回给调用者但当前线程不会被阻塞可以去处理其他工作。当await的任务完成后该方法会从暂停的地方继续执行可能在原线程也可能在新线程由同步上下文决定。Task/TaskT表示一个异步操作。Task无返回值TaskT返回类型为T的值。4.2 从同步到异步一个文件读取的对比// 同步版本 - 在UI线程调用会导致界面冻结 public string ReadFileSynchronously(string filePath) { // Thread.Sleep 模拟耗时CPU操作这是错误示范会阻塞线程 // Thread.Sleep(5000); // 不要这样做 // 同步文件读取会阻塞线程直到文件读完 string content File.ReadAllText(filePath); return content; } // 异步版本 - 不会阻塞调用线程 public async Taskstring ReadFileAsynchronously(string filePath) { // 异步文件读取await 会释放当前线程 string content await File.ReadAllTextAsync(filePath); return content; } // 在UI事件处理函数中调用如WPF/WinForms/MAUI按钮点击事件 private async void btnReadFile_Click(object sender, EventArgs e) { lblStatus.Text Reading file...; btnReadFile.Enabled false; try { // 调用异步方法UI线程不会被阻塞 string content await ReadFileAsynchronously(largefile.txt); txtContent.Text content; lblStatus.Text File read successfully.; } catch (FileNotFoundException ex) { lblStatus.Text $File not found: {ex.Message}; } finally { btnReadFile.Enabled true; } // 注意事件处理函数本身是 async void这是特例通常方法应返回 Task。 }4.3 常见误区与最佳实践误区1async void滥用只有事件处理程序如按钮点击才应使用async void。其他所有异步方法都应返回Task或TaskT。因为async void方法无法被等待其内部抛出的异常会直接触发进程级的未处理异常事件难以捕获。// 错误 public async void ProcessDataAsync() { /* ... */ } // 正确 public async Task ProcessDataAsync() { /* ... */ }误区2在同步方法中调用.Result或.Wait()这会导致死锁风险尤其是在拥有同步上下文的环境中如UI线程、ASP.NET Core旧版本。// 危险可能导致死锁 public string GetData() { var task SomeAsyncMethod(); // 返回 Taskstring return task.Result; // 同步阻塞等待任务完成 } // 正确做法让同步方法也变成异步方法并一直使用 await public async Taskstring GetDataAsync() { return await SomeAsyncMethod(); } // 如果无法修改调用链如在Main方法中可以使用 .GetAwaiter().GetResult() // 但这仍然是阻塞的应作为最后手段。 public void MainMethod() { var result SomeAsyncMethod().GetAwaiter().GetResult(); }误区3不必要地包装同步代码为Task.RunTask.Run是将CPU密集型工作推送到线程池的好方法但不应滥用它来包装本身就是异步的I/O操作或已经很快的同步操作。// 不必要的包装 public async Taskstring ReadFileAsync(string path) { return await Task.Run(() File.ReadAllText(path)); // File.ReadAllText 是同步的这会占用线程池线程 } // 更好的做法使用真正的异步API public async Taskstring ReadFileAsync(string path) { return await File.ReadAllTextAsync(path); // 使用异步版本 } // 正确的使用场景CPU密集型计算 public async Taskint CalculatePrimeCountAsync(int limit) { // 这是一个耗时的CPU计算适合放到线程池 return await Task.Run(() { int count 0; for (int i 2; i limit; i) { if (IsPrime(i)) count; } return count; }); }最佳实践配置等待ConfigureAwait在类库代码中如果不关心后续代码在哪个上下文如UI线程中恢复应使用ConfigureAwait(false)。这可以避免不必要的线程上下文切换提升性能并有助于避免死锁。public async Taskstring GetDataFromNetworkAsync() { using var httpClient new HttpClient(); // 在类库中我们通常不关心回到UI线程所以使用 ConfigureAwait(false) string result await httpClient.GetStringAsync(https://api.example.com/data) .ConfigureAwait(false); // 这里的后续代码会在线程池线程上执行而不是UI线程 return ProcessData(result); }注意在UI层如按钮事件处理程序中通常需要回到UI线程来更新控件所以不要使用ConfigureAwait(false)。5. 实战构建一个简单的异步数据处理器让我们综合运用以上知识构建一个模拟的“产品数据处理器”。它从文件异步读取产品数据进行过滤和转换然后异步保存到另一个文件并处理可能出现的异常。using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; // 使用 System.Text.Json 进行序列化 using System.Threading.Tasks; namespace AdvancedCSharpDemo { public class ProductProcessor { private readonly ILogger _logger; public ProductProcessor(ILogger logger) { _logger logger; } // 主处理流程 public async Task ProcessProductsAsync(string inputFilePath, string outputFilePath) { if (string.IsNullOrWhiteSpace(inputFilePath)) throw new ArgumentException(Input file path cannot be empty., nameof(inputFilePath)); _logger.LogInfo($Starting to process products from {inputFilePath}...); ListProduct products; try { // 1. 异步读取文件 products await ReadProductsFromFileAsync(inputFilePath); } catch (FileNotFoundException ex) { _logger.LogError($Input file not found: {ex.Message}); throw new DomainException($Cannot find input file: {inputFilePath}, ex, FILE_NOT_FOUND); } catch (JsonException ex) { _logger.LogError($Invalid JSON format in file: {ex.Message}); throw new DomainException($Invalid product data format in file., ex, INVALID_DATA); } if (products null || !products.Any()) { _logger.LogWarning(No products found in the input file.); return; } _logger.LogInfo($Loaded {products.Count} products.); // 2. 使用LINQ进行数据处理 var processedData ProcessProductData(products); // 3. 异步写入结果 await SaveProcessedDataAsync(processedData, outputFilePath); _logger.LogInfo($Processing completed. Results saved to {outputFilePath}.); } private async TaskListProduct ReadProductsFromFileAsync(string filePath) { // 模拟异步读取 await Task.Delay(100); // 模拟I/O延迟 if (!File.Exists(filePath)) { throw new FileNotFoundException($The file {filePath} does not exist.); } string jsonContent; try { jsonContent await File.ReadAllTextAsync(filePath); } catch (IOException ex) { throw new DomainException($Cannot read file {filePath}., ex, IO_ERROR); } var options new JsonSerializerOptions { PropertyNameCaseInsensitive true }; var products JsonSerializer.DeserializeListProduct(jsonContent, options); return products ?? new ListProduct(); // 如果反序列化结果为null返回空列表 } private ListProcessedProductInfo ProcessProductData(ListProduct products) { // 使用LINQ进行复杂的查询和转换 return products .Where(p p.Stock 0) // 只处理有库存的产品 .GroupBy(p p.Category) .Select(g new ProcessedProductInfo { Category g.Key, TotalValueInStock g.Sum(p p.Price * p.Stock), AveragePrice g.Average(p p.Price), Products g.OrderByDescending(p p.Price) .Select(p new ProductSummary { Id p.Id, Name p.Name, Price p.Price, Stock p.Stock }).ToList() }) .OrderByDescending(info info.TotalValueInStock) .ToList(); } private async Task SaveProcessedDataAsync(ListProcessedProductInfo data, string outputFilePath) { var options new JsonSerializerOptions { WriteIndented true }; string jsonOutput JsonSerializer.Serialize(data, options); try { // 确保目录存在 var directory Path.GetDirectoryName(outputFilePath); if (!string.IsNullOrEmpty(directory) !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } await File.WriteAllTextAsync(outputFilePath, jsonOutput); } catch (IOException ex) { throw new DomainException($Cannot write to output file {outputFilePath}., ex, IO_ERROR); } } } // 辅助类 public class ProcessedProductInfo { public string Category { get; set; } public decimal TotalValueInStock { get; set; } public decimal AveragePrice { get; set; } public ListProductSummary Products { get; set; } new ListProductSummary(); } public class ProductSummary { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public int Stock { get; set; } } // 简单的日志接口 public interface ILogger { void LogInfo(string message); void LogWarning(string message); void LogError(string message); } // 使用示例 public class Program { static async Task Main(string[] args) { var logger new ConsoleLogger(); var processor new ProductProcessor(logger); try { await processor.ProcessProductsAsync(products.json, output/results.json); Console.WriteLine(Processing succeeded.); } catch (DomainException ex) { Console.WriteLine($Business error ({ex.ErrorCode}): {ex.Message}); } catch (Exception ex) { Console.WriteLine($Unexpected error: {ex.Message}); } } } public class ConsoleLogger : ILogger { public void LogError(string message) Console.WriteLine($[ERROR] {DateTime.Now}: {message}); public void LogInfo(string message) Console.WriteLine($[INFO] {DateTime.Now}: {message}); public void LogWarning(string message) Console.WriteLine($[WARN] {DateTime.Now}: {message}); } }6. 常见问题与排查思路问题现象可能原因排查方式解决方案LINQ查询性能慢1. 延迟执行导致重复查询数据库/耗时操作。2. 在循环内执行LINQ查询N1问题。3. 对大数据集使用ToList()过早导致内存压力。1. 使用性能分析工具如Visual Studio Profiler。2. 检查SQL Profiler如果使用EF Core查看生成的SQL语句。1. 对需要重用的查询结果及时使用ToList()或ToArray()物化。2. 使用Include或投影Select一次性加载关联数据避免N1查询。3. 考虑使用分页Skip/Take。async/await导致UI仍然卡顿1. 在await后执行了耗时的同步CPU工作。2. 错误地使用了.Result或.Wait()导致死锁。1. 检查await后的代码是否包含密集循环或复杂计算。2. 检查调用栈中是否有同步阻塞调用。1. 将CPU密集型工作用Task.Run包装并await。2. 确保异步调用链一路async/await到底避免同步阻塞。在类库代码中使用ConfigureAwait(false)。集合修改时抛出InvalidOperationException在foreach循环中直接修改正在遍历的集合如List。查看异常堆栈定位到修改集合的代码行。1. 遍历集合的副本foreach(var item in list.ToList())。2. 将要删除/修改的元素先记录在另一个列表中循环结束后再处理。多线程下集合操作不安全多个线程同时读写非线程安全的集合如ListT。观察随机出现的索引越界、数据损坏等异常。1. 使用线程安全集合ConcurrentBagT,ConcurrentDictionaryTKey,TValue。2. 使用锁lock来保护对集合的访问。自定义异常信息不清晰异常Message过于简单没有包含关键的业务数据。查看异常日志判断是否能快速定位问题。在自定义异常的构造函数中将相关参数如ID、状态等格式化到Message中或作为异常属性暴露。内存泄漏对象未释放1. 事件注册后未注销。2. 静态集合长期持有对象引用。3. 非托管资源未正确实现IDisposable。使用内存分析工具如 dotMemory, Visual Studio Diagnostic Tools。1. 事件订阅者生命周期短于发布者时记得取消订阅。2. 定期清理静态缓存。3. 正确实现Dispose模式并在using语句中使用。7. 最佳实践与工程建议面向接口编程依赖抽象接口而非具体实现。这提高了代码的可测试性和可扩展性。上面的ILogger就是一个简单例子。遵循单一职责原则一个类只应有一个引起它变化的原因。如果一个类负责用户认证、数据验证、邮件发送和日志记录它就已经违反了此原则。善用using语句对于实现了IDisposable接口的对象如文件流、数据库连接、HttpClient在某些场景下务必使用using语句确保资源被及时释放。using (var stream new FileStream(file.txt, FileMode.Open)) { // 使用 stream } // 离开作用域时自动调用 stream.Dispose() // C# 8.0 起可以使用 using 声明更简洁 using var stream new FileStream(file.txt, FileMode.Open);为异步方法添加Async后缀这是一个广泛接受的命名约定如GetDataAsync方便区分同步和异步方法。谨慎使用静态类和静态成员静态成员在整个应用程序域生命周期内存在可能导致状态难以管理和测试困难。优先考虑依赖注入。编写单元测试对核心业务逻辑如ProcessProductData方法编写单元测试。使用测试框架如 xUnit, NUnit, MSTest和模拟框架如 Moq, NSubstitute。良好的设计如依赖注入会让代码更容易测试。代码格式化与命名规范使用一致的代码风格。遵循 C# 命名约定类名PascalCase局部变量camelCase常量UPPER_CASE。使用.editorconfig文件来统一团队规范。从理解语法到精通设计从能跑通代码到构建健壮应用C#进阶之路的关键在于思维的转变。不再满足于“它能工作”而要追求“它工作得好、易于理解和修改”。本文探讨的面向对象设计、异常处理、LINQ和异步编程正是支撑这一转变的四大支柱。掌握它们你编写的将不再是简单的脚本而是结构清晰、应对变化、高效可靠的专业级软件。下一步你可以深入探索依赖注入、单元测试、Entity Framework Core、ASP.NET Core Web API 开发等更具体的领域技术将这些核心原则应用到实际的项目框架中你的开发能力将迎来质的飞跃。