整个2025年,PVS-Studio团队一直在积极检查开源的C#项目。在一年中,我们发现了很多缺陷,因此,我们从这个巨大的品种中挑选了十个最有趣的项目。 我们如何编制顶部? 有几个项目代码必须满足的标准,以获得我们顶级名单中的位置: 它来自一个开源项目; 这些问题是由PVS研究所发现的; 代码最有可能包含错误; 代码有趣的检查; 每个错误都是独一无二的。 由于我们定期编制此类列表,我们收集了令人印象深刻的奇怪错误集,您可以在这里阅读前几年的文章: 2024年的十大错误 2023年的十大错误 2022年十大错误 2021年十大错误 2020年十大错误 2019年十大错误 现在,让我们潜入2025年的C#错误的迷人的深渊! P.S.文章作者根据他的主观意见选择并组合了错误.如果你认为这个或那个错误值得另一个地方,请放心留言:) 第10章 试试试试 今天的顶部以在中提到的错误开始 .NET 9 似乎刚刚发布,但一个多月前, .NET 10 取代了它。 . 关于检查 .NET 9 的文章 此文章 让我们回到分析: public static void SetAsIConvertible(this ref ComVariant variant, IConvertible value) { TypeCode tc = value.GetTypeCode(); CultureInfo ci = CultureInfo.CurrentCulture; switch (tc) { case TypeCode.Empty: break; case TypeCode.Object: variant = ComVariant.CreateRaw(....); break; case TypeCode.DBNull: variant = ComVariant.Null; break; case TypeCode.Boolean: variant = ComVariant.Create<bool>(....)); break; case TypeCode.Char: variant = ComVariant.Create<ushort>(value.ToChar(ci)); break; case TypeCode.SByte: variant = ComVariant.Create<sbyte>(value.ToSByte(ci)); break; case TypeCode.Byte: variant = ComVariant.Create<byte>(value.ToByte(ci)); break; case TypeCode.Int16: variant = ComVariant.Create(value.ToInt16(ci)); break; case TypeCode.UInt16: variant = ComVariant.Create(value.ToUInt16(ci)); break; case TypeCode.Int32: variant = ComVariant.Create(value.ToInt32(ci)); break; case TypeCode.UInt32: variant = ComVariant.Create(value.ToUInt32(ci)); break; case TypeCode.Int64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.UInt64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.Single: variant = ComVariant.Create(value.ToSingle(ci)); break; case TypeCode.Double: variant = ComVariant.Create(value.ToDouble(ci)); break; case TypeCode.Decimal: variant = ComVariant.Create(value.ToDecimal(ci)); break; case TypeCode.DateTime: variant = ComVariant.Create(value.ToDateTime(ci)); break; case TypeCode.String: variant = ComVariant.Create(....); break; default: throw new NotSupportedException(); } } 进入全屏模式 退出全屏模式 你能看到这个问题吗?它肯定在那里! case TypeCode.Int64: variant = ComVariant.Create(value.ToInt64(ci)); break; case TypeCode.UInt64: variant = ComVariant.Create(value.ToInt64(ci)); break; // <= 进入全屏模式 退出全屏模式 PVS研究室警告: 两个或多个案例分支执行相同的操作. DynamicVariantExtensions.cs 68 第3139章 我希望你给了你的眼睛一个好的锻炼,你的尖锐的视力没有让你下来。 相反, , developers should have used the existing 方法:这可能是复制文件错误。 case TypeCode.UInt64 value.ToInt64 ToUInt64() 9th place. Invalid format 第九个位置是描述在 : 关于检查NEO和NBitcoin项目的文章 public override string ToString() { var sb = new StringBuilder(); sb.AppendFormat("{1:X04} {2,-10}{3}{4}", Position, OpCode, DecodeOperand()); return sb.ToString(); } Enter fullscreen mode Exit fullscreen mode PVS研究室警告: [CWE-685] 格式不正确 在调用“AppendFormat”函数时,预计会出现不同的格式项目。 第3025章 Calling the overridden method inevitably causes an exception. This is due to an incorrect 电话包含两个错误。 ToString sb.AppendFormat 要插入的参数数小于格式字符串中的位数,这导致例外。 即使我们通过匹配参数和位数来解决第一个问题,呼叫仍然会抛出例外,因为位数索引从0开始,而不是1。 第8章 游泳 下一个错误来自于 : 关于测试 lean 交易引擎的文章 public override int GetHashCode() { unchecked { var hashCode = Definition.GetHashCode(); var arr = new int[Legs.Count]; for (int i = 0; i < Legs.Count; i++) { arr[i] = Legs[i].GetHashCode(); } Array.Sort(arr); for (int i = 0; i < arr.Length; i++) { hashCode = (hashCode * 397) ^ arr[i]; } return hashCode; } } public override bool Equals(object obj) { .... return Equals((OptionStrategyDefinitionMatch) obj); } 进入全屏模式 退出全屏模式 PVS研究室警告: “Legs”属性在“GetHashCode”方法中使用,但在“Equals”方法中缺少。 第3192章 分析师在一 检查在 方法,称之为过度 ,并发现它不使用 财产,尽管 依靠它。 程序间 Equals Equals Legs GetHashCode 让我们更仔细地看看该 方法: Equals public bool Equals(OptionStrategyDefinitionMatch other) { .... var positions = other.Legs .ToDictionary(leg => leg.Position, leg => leg.Multiplier); foreach (var leg in other.Legs) // <= { int multiplier; if (!positions.TryGetValue(leg.Position, out multiplier)) { return false; } if (leg.Multiplier != multiplier) { return false; } } return true; } 进入全屏模式 退出全屏模式 请注意,该方法 iterates 对于该集合中的每个元素,代码试图在 字典 - 但这个字典也来自于 因此,代码检查集合的元素是否存在于同一集合中。 other.Legs positions other.Legs 我们可以通过更换代码来修复 with 在标记位置。 other.Legs Legs 第7章 平等平等 第七个位置是从一个错误。 : 关于检查ScottPlot的文章 public class CoordinateRangeMutable : IEquatable<CoordinateRangeMutable> { .... public bool Equals(CoordinateRangeMutable? other) { if (other is null) return false; return Equals(Min, other.Min) && Equals(Min, other.Min); // <= } public override bool Equals(object? obj) { if (obj is null) return false; if (obj is CoordinateRangeMutable other) return Equals(other); return false; } public override int GetHashCode() { return Min.GetHashCode() ^ Max.GetHashCode(); // <= } } 进入全屏模式 退出全屏模式 PVS研究室警告: “Max”属性在“GetHashCode”方法中使用,但在“Equals”方法中缺少。 第3192章 There are identical sub-expressions 'Equals(Min, other.Min)' to the left and to the right of the '&&' operator. ScottPlot CoordinateRangeMutable.cs 172 第3001章 分析师为此代码片段发布了两个警告,让我们看看为什么会发生这种情况。 我们将开始与 分析师警告说, 财产被用在 方法,但不是在 方法:如果我们观察过度 method, we can see that another 在他的身体里,我们可以看到如下: . The 诊断规则强调了这一段落。 第3192章 Max GetHashCode Equals Equals Equals Equals(Min, other.Min) && Equals(Min, other.Min) 第3001章 显然,其中一个 操作员必须有 形式。 && Equals(Max, other.Max) 分析师是对的, 它不会出现在在 方法。 Max Equals 第6章 玩点小玩意儿 一个错误,就像前一个错误一样,是从 ,结束了前半部分的顶点: 关于检查ScottPlot的文章 public static Interactivity.Key GetKey(this Keys keys) { Keys keyCode = keys & ~Keys.Modifiers; // <= Interactivity.Key key = keyCode switch { Keys.Alt => Interactivity.StandardKeys.Alt, // <= Keys.Menu => Interactivity.StandardKeys.Alt, Keys.Shift => Interactivity.StandardKeys.Shift, // <= Keys.ShiftKey => Interactivity.StandardKeys.Shift, Keys.LShiftKey => Interactivity.StandardKeys.Shift, Keys.RShiftKey => Interactivity.StandardKeys.Shift, Keys.Control => Interactivity.StandardKeys.Control, // <= Keys.ControlKey => Interactivity.StandardKeys.Control, Keys.Down => Interactivity.StandardKeys.Down, Keys.Up => Interactivity.StandardKeys.Up, Keys.Left => Interactivity.StandardKeys.Left, Keys.Right => Interactivity.StandardKeys.Right, _ => Interactivity.StandardKeys.Unknown, }; .... } 进入全屏模式 退出全屏模式 PVS研究室警告: 无法达到的代码被检测到。“案例”值不在匹配表达式的范围内。ScottPlot.WinForms FormsPlotExtensions.cs 106 第3202章 内部的多个模式值 在当前的情况下,这是不可能的,让我们看看这里发生了什么。 switch 首先,我们应该看看对应列表中的错误元素的值。 [Flags] [TypeConverter(typeof(KeysConverter))] [Editor(....)] public enum Keys { /// <summary> /// The bit mask to extract modifiers from a key value. /// </summary> Modifiers = unchecked((int)0xFFFF0000), .... /// <summary> /// The SHIFT modifier key. /// </summary> Shift = 0x00010000, /// <summary> /// The CTRL modifier key. /// </summary> Control = 0x00020000, /// <summary> /// The ALT modifier key. /// </summary> Alt = 0x00040000 } 进入全屏模式 退出全屏模式 接下来,让我们将它们转换为二进制: 现在很明显, 包含所有错误的列表元素。 Modifiers 价值转移到 is obtained from the 这个表达式排除了 价值从 此外,除了 , however, , ,和 也将被排除在外,因为 已包含这些值( ) 每个非零位的错误列表元素都有一个非零位)。 switch keys & ~Keys.Modifiers Keys.Modifiers keys Keys.Modifiers Shift Control Alt Modifiers Modifiers 从所有这些我们可以得出结论,即生产的比特组合 , ,或 对于The 手术不存在。 Shift Control Alt keys & ~Keys.Modifiers 问题可能在于 实施,而不是列举值。 switch 第5章 全部被打倒了 前五名以一个错误提到开始 : 关于检查 .NET 9 的文章 struct StackValue { .... public override bool Equals(object obj) { if (Object.ReferenceEquals(this, obj)) return true; if (!(obj is StackValue)) return false; var value = (StackValue)obj; return this.Kind == value.Kind && this.Flags == value.Flags && this.Type == value.Type; } } 进入全屏模式 退出全屏模式 PVS研究室警告: 将值类型变量与“ReferenceEquals”进行比较是错误的,因为“This”将被列入框中。 第3161章 该 该方法采用了对 类型. 当一个值类型被传递时,它会得到 在堆栈上创建的参考不会匹配任何其他参考。 ReferenceEquals Object 拳击 自从 作为第一个争论,拳击每次都会发生。 这个方法叫做“检查”。 总是回来 . this Equals ReferenceEquals false 请注意,此问题不会影响该方法的运作方式。 方法是为了避免进一步的比较,如果参照是相同的。换句话说,这是一种优化。 ReferenceEquals 代码在检查后总是执行; 每个对等的调用结果是一个拳击操作。 很有趣的是,分析器嵌入到 .NET(规则)中。 )也检测到这个问题,但是 .NET 的开发者自己也无法避免它:) 第2013章 这个规则可能已被禁用为该项目。 is enabled by default starting with .NET 5. 第2013章 第4章 没有订阅 第四个位置是从一个错误到一个 : 关于检查MSBuild的文章 private static void SubscribeImmutablePathsInitialized() { NotifyOnScopingReadiness?.Invoke(); FileClassifier.Shared.OnImmutablePathsInitialized -= () => NotifyOnScopingReadiness?.Invoke(); } 进入全屏模式 退出全屏模式 PVS研究室警告: . Anonymous function is used to unsubscribe from 'OnImmutablePathsInitialized' event. No handlers will be unsubscribed, as a separate delegate instance is created for each anonymous function declaration. CheckScopeClassifier.cs 67 V3084 在这种情况下,退出代表的订阅不会生效,因为每次宣布匿名函数时,会创建一个新的代表实例,因此,尝试退出代表的订阅不会产生预期的效果。 已签署到 but unsubscribes from , which has no effect. OnImmutablePathsInitialized delegate 1 delegate 2 第3位:对操作员优先权的混淆 So, we've reached the top three. The error from an 获得荣誉的第三名: 关于检查NEO和NBitcoin项目的文章 public override int Size => base.Size + ChangeViewMessages?.Values.GetVarSize() ?? 0 + 1 + PrepareRequestMessage?.Size ?? 0 + PreparationHash?.Size ?? 0 + PreparationMessages?.Values.GetVarSize() ?? 0 + CommitMessages?.Values.GetVarSize() ?? 0; 进入全屏模式 退出全屏模式 PVS-Studio warning: [CWE-783]也许“??”操作员的工作方式不同于预期。它的优先级低于其左侧的其他操作员的优先级。 第3123章 The analyzer issued several 对这个代码的警告,但我只包括了一个短暂性。 operator has lower precedence than the 然而,这个表达式的格式化表明,开发人员预计相反。 第3123章 ?? + Does the order of operations matter here? To answer the question, let's look at the example of a sub-expression addition if is : ChangeViewMessages null base.Size + ChangeViewMessages?.Values.GetVarSize() ?? 0 Enter fullscreen mode Exit fullscreen mode 无论其中的 值,子表达式的结果总是为0,因为添加 to 结果在 . base.Size base.Size null null 如果我们放置 在拼接中,改变操作顺序,结果成为 . ChangeViewMessages?.Values.GetVarSize() ?? 0 base.Size 第2章 背叛的模式 第二個地方是從一個錯誤到一個 : 关于检查文件管理器的文章 protected void ChangeMode(OmnibarMode? oldMode, OmnibarMode newMode) { .... var modeSeparatorWidth = itemCount is not 0 or 1 ? _modesHostGrid.Children[1] is FrameworkElement frameworkElement ? frameworkElement.ActualWidth : 0 : 0; .... } 进入全屏模式 退出全屏模式 PVS-Studio warning: [CWE-670] The 'not 0 or 1' logical pattern may not work as expected. The 'not' pattern is matched only to the first expression from the 'or' pattern. Files.App.Controls Omnibar.cs 149 第3207章 让我们更仔细地看看该 已经猜到了问题是什么? 这个模式是多余的。 它的第二部分没有影响。 itemCount is not 0 or 1 当说“x不是0或1”时,人们通常暗示x既不是0也不是1。 实际上意味着 这些错误不仅会导致裁员,还会导致诸如 这个问题甚至在一 根据讨论,要么编译器可能会在未来捕捉到这个问题,要么内置的静态分析会作为警告标记。 x is not 0 or 1 x is (not 0) or 1 NullReferenceException: list is not null or list.Count == 0 会议 第1章 左边是怎么工作的? 胜利者是错误的。 . This error ranks first due to its subtlety. Some developers may not consider the effects of using deferred execution methods in combination with captured variables. All the details are below: 关于检查Lean交易引擎的文章 public void FutureMarginModel_MarginEntriesValid(string market) { .... var lineNumber = 0; var errorMessageTemplate = $"Error encountered in file " + $"{marginFile.Name} on line "; var csv = File.ReadLines(marginFile.FullName) .Where(x => !x.StartsWithInvariant("#") && !string.IsNullOrWhiteSpace(x)) .Skip(1) .Select(x => { lineNumber++; // <= .... }); lineNumber = 0; // <= foreach (var line in csv) { lineNumber++; // <= .... } } 进入全屏模式 退出全屏模式 PVS-Studio warning: “lineNumber”变量在以延迟执行的 LINQ 方法捕获后被更改. 执行该方法时不会使用原始值。 第3219章 该 variable is captured and incremented in the delegate that is passed to the LINQ method. Since is a deferred method, the delegate code runs while iterating over the resulting collection, not when gets called. lineNumber Select Select 當它在它們之上重複時, 收藏,The 变量也增加了,因此每一次迭代都增加了 :当代表跑步和内部 ,这看起来很奇怪。 csv lineNumber lineNumber by 2 foreach 注意到 任命前 . It's likely that developers expected that this variable could hold a non-zero value before the loop. However, that is impossible: starts at zero, and the only place that changes it before the 如上所述,代表在迭代过程中运行,而不是在它之前。 lineNumber = 0 foreach lineNumber foreach Conclusion 这就是它!我们从作者的观点来看,通过了最有趣的警告:) 我希望你在代码开发方面发现这个收藏很有趣和引人注目的。 如果您想检查项目是否存在类似问题,现在是使用静态分析器的时候了。 . 下载链接