位置: 文档库 > C#(.NET) > C# 程序员最常犯的 10 个错误

C# 程序员最常犯的 10 个错误

StackOverflowed 上传于 2025-07-24 01:34

《C# 程序员最常犯的 10 个错误》

C# 作为微软主推的现代化编程语言,凭借其强大的.NET 框架支持和简洁的语法特性,已成为企业级应用开发、游戏开发(Unity)和云服务领域的热门选择。然而,即使是经验丰富的开发者,也容易在代码中埋下隐患。本文将系统性梳理 C# 程序员最常犯的 10 个错误,涵盖语法陷阱、性能问题、设计缺陷和安全漏洞,帮助开发者提升代码质量。

1. 空引用异常(NullReferenceException)

空引用异常是 C# 开发中最常见的运行时错误,通常由未初始化的对象或未检查的返回值引发。

// 错误示例:未检查对象是否为 null
string name = GetName(); // 假设可能返回 null
Console.WriteLine(name.Length); // 抛出 NullReferenceException

// 正确做法:使用 null 条件运算符或显式检查
Console.WriteLine(name?.Length ?? 0); // C# 6.0+ 语法
// 或
if (name != null)
{
    Console.WriteLine(name.Length);
}

防御性编程建议:

  • 启用 C# 8.0 的可空引用类型(Nullable Reference Types)
  • 使用 TryGet 模式替代直接返回 null
  • 通过代码分析工具(如 Roslynator)自动检测潜在空引用

2. 异步编程中的常见陷阱

随着异步编程的普及,async/await 模式的使用不当已成为主要错误源。

// 错误示例 1:同步方法标记为 async
public int BadAsyncMethod()
{
    return 42; // 警告:此方法应标记为 async
}

// 错误示例 2:未正确处理异步上下文
public async Task ProcessData()
{
    var data = await GetDataAsync().ConfigureAwait(false);
    // 若后续代码依赖 UI 线程上下文,此处会崩溃
    uiControl.Text = data; 
}

// 正确做法:明确配置上下文
public async Task ProcessDataSafely()
{
    var data = await GetDataAsync().ConfigureAwait(false);
    await Task.Run(() => uiControl.Text = data); // 切换回 UI 线程
}

最佳实践:

  • 顶层方法应始终返回 Task 而非 void
  • 使用 ValueTask 优化高频短路径异步操作
  • 通过 AsyncFixer 扩展自动修复常见异步问题

3. 集合操作的性能误区

不恰当的集合操作会导致严重的性能下降,尤其在大数据量场景下。

// 错误示例:在循环中频繁创建集合
foreach (var item in largeCollection)
{
    var tempList = new List(); // 每次循环都创建新实例
    tempList.Add(item.Value);
    // ...
}

// 错误示例:使用 LINQ 的 ToList() 不当
var filtered = data.Where(x => x > 10).ToList(); // 立即物化
var sorted = filtered.OrderBy(x => x).ToList(); // 再次物化

// 正确做法:延迟执行 + 单一物化
var optimized = data
    .Where(x => x > 10)
    .OrderBy(x => x)
    .ToList();

优化建议:

  • 预分配集合容量:var list = new List(initialCapacity)
  • 优先使用 Array.Pool 处理临时数组
  • 通过 BenchmarkDotNet 测量集合操作性能

4. 字符串处理的常见错误

字符串操作看似简单,实则暗藏性能陷阱和编码问题。

// 错误示例 1:字符串拼接滥用
string result = "";
foreach (var s in strings)
{
    result += s; // 每次循环创建新字符串对象
}

// 正确做法:使用 StringBuilder
var sb = new StringBuilder();
foreach (var s in strings)
{
    sb.Append(s);
}
string result = sb.ToString();

// 错误示例 2:未指定字符串比较规则
bool isEqual = "test".Equals("TEST"); // 默认区分大小写
bool caseInsensitive = "test".Equals("TEST", StringComparison.OrdinalIgnoreCase);

// 错误示例 3:硬编码编码
var bytes = Encoding.Default.GetBytes("文本"); // 依赖系统默认编码
var safeBytes = Encoding.UTF8.GetBytes("文本");

关键原则:

  • 超过 3 次拼接必须使用 StringBuilder
  • 始终显式指定字符串比较规则
  • 统一使用 UTF-8 编码处理文本

5. 资源泄漏与 IDisposable 误用

未正确释放资源是导致内存泄漏和文件锁定的主要原因。

// 错误示例 1:未实现 IDisposable
public class ResourceHolder // 缺少 : IDisposable
{
    private FileStream _stream;
    // ...
}

// 错误示例 2:错误的 Dispose 实现
public void Dispose()
{
    _stream.Dispose(); // 若 _stream 为 null 会抛出异常
    // 缺少 GC.SuppressFinalize
}

// 正确模式:
public class SafeResource : IDisposable
{
    private bool _disposed = false;
    private FileStream _stream;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _stream?.Dispose();
            }
            _disposed = true;
        }
    }
}

简化方案:

  • 使用 using 语句自动释放
  • 通过 IAsyncDisposable 处理异步资源
  • 利用分析工具检测未释放资源

6. 多线程编程的同步问题

并发编程中的竞态条件和死锁是复杂系统的头号敌人。

// 错误示例 1:锁范围过大
private static readonly object _lock = new object();

public void Process()
{
    lock (_lock)
    {
        // 长时间操作导致其他线程阻塞
        Thread.Sleep(1000); 
    }
}

// 错误示例 2:双重检查锁定模式错误
private static volatile MyClass _instance;

public static MyClass Instance
{
    get
    {
        if (_instance == null)
        {
            lock (_lock)
            {
                if (_instance == null) // 仍可能出错
                {
                    _instance = new MyClass();
                }
            }
        }
        return _instance;
    }
}

// 正确做法:使用 Lazy
private static readonly Lazy _lazyInstance = 
    new Lazy(() => new MyClass());

public static MyClass Instance => _lazyInstance.Value;

并发编程建议:

  • 优先使用 ConcurrentCollections
  • 通过 SemaphoreSlim 实现限流
  • 使用 Thread.VolatileRead/Write 处理低级同步

7. 异常处理的滥用与误用

不恰当的异常处理会掩盖问题或导致性能下降。

// 错误示例 1:捕获所有异常
try
{
    // 代码
}
catch (Exception ex) // 过于宽泛
{
    Log(ex); // 可能掩盖严重错误
    return default;
}

// 错误示例 2:异常用于流程控制
try
{
    int.Parse("abc"); // 预期可能失败的操作
}
catch (FormatException)
{
    // 不应使用异常处理可预见的流程
}

// 正确做法:
int result;
if (int.TryParse("123", out result))
{
    // 成功处理
}
else
{
    // 失败处理
}

异常处理原则:

  • 只为异常情况使用异常
  • 按从具体到一般的顺序捕获异常
  • 记录完整的异常堆栈信息

8. LINQ 查询的效率问题

LINQ 的延迟执行特性可能导致意外的性能问题。

// 错误示例 1:多次枚举 IEnumerable
var query = data.Where(x => x > 10);
var count = query.Count(); // 第一次枚举
var first = query.First(); // 第二次枚举(重新执行查询)

// 错误示例 2:N+1 查询问题
var customers = dbContext.Customers.ToList();
foreach (var c in customers)
{
    var orders = dbContext.Orders // 每次循环都查询数据库
        .Where(o => o.CustomerId == c.Id)
        .ToList();
}

// 正确做法:
var customersWithOrders = dbContext.Customers
    .Include(c => c.Orders) // EF Core 预加载
    .ToList();

LINQ 优化技巧:

  • 对频繁使用的查询结果调用 ToList()ToArray()
  • 使用 AsParallel() 实现并行查询(谨慎使用)
  • 通过 Expression 树优化复杂查询

9. 依赖注入的常见错误

不正确的依赖注入配置会导致服务定位器反模式和生命周期混乱。

// 错误示例 1:服务定位器反模式
public class BadController
{
    private readonly ILogger _logger;
    
    public BadController()
    {
        _logger = ServiceLocator.Resolve(); // 反模式
    }
}

// 错误示例 2:错误的生命周期配置
services.AddTransient(); // 数据库上下文应为 Scoped

// 正确做法:
public class GoodController : Controller
{
    private readonly ILogger _logger;
    
    public GoodController(ILogger logger) // 构造函数注入
    {
        _logger = logger;
    }
}

// Startup.cs 配置
services.AddDbContext(
    options => options.UseSqlServer(Configuration.GetConnectionString("Default")),
    ServiceLifetime.Scoped);

DI 最佳实践:

  • 遵循显式依赖原则
  • 避免在中间件中解析服务
  • 使用 IOptions 配置选项

10. 安全漏洞:注入攻击与敏感数据

安全编码意识不足可能导致严重漏洞。

// 错误示例 1:SQL 注入
var name = Request.Query["name"];
var sql = $"SELECT * FROM Users WHERE Name = '{name}'"; // 危险!

// 正确做法:参数化查询
var sql = "SELECT * FROM Users WHERE Name = @Name";
using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@Name", name);

// 错误示例 2:硬编码密码
var connectionString = "Server=...;User ID=sa;Password=P@ssw0rd;";

// 正确做法:使用用户机密或密钥库
var connectionString = Configuration.GetConnectionString("Default");

安全编码要点:

  • 始终验证和清理用户输入
  • 使用加密库处理敏感数据
  • 定期更新安全依赖项

总结与提升建议

避免这些常见错误需要结合工具使用和编码规范:

  • 启用编译器警告为错误(/warnaserror
  • 使用 SonarLint、ReSharper 等静态分析工具
  • 建立代码审查流程和单元测试覆盖率要求
  • 持续学习.NET 最新特性和安全实践

关键词:C#编程错误、空引用异常、异步编程陷阱集合性能优化、字符串处理、资源泄漏、多线程同步、异常处理、LINQ查询效率、依赖注入配置、安全编码

简介:本文系统梳理C#开发者最常犯的10类错误,涵盖空引用处理、异步编程、集合操作、字符串处理、资源管理、多线程同步异常处理、LINQ查询、依赖注入和安全编码等核心场景,提供错误示例、正确实践和优化建议,帮助开发者提升代码质量和系统稳定性。