VB.NET自定义IComparer实现自然排序失效,求排查帮助
自然排序比较器失效的排查与修复
当前字符串排序结果如下:
Slide1.jpg Slide10.jpg Slide11.jpg Slide12.jpg Slide2.jpg Slide3.jpg
需实现自然排序(按数字大小顺序排列),自行实现的IComparer(Of String)接口实现类NaturalComparer未生效,使用代码及原比较器实现如下:
使用代码
Dim files = Directory.GetFiles("c:\PowerPoint\Images", "*.jpg", SearchOption.AllDirectories).ToList() files.Sort(New NaturalComparer(True))
原NaturalComparer实现
Public Class NaturalComparer Implements IComparer(Of String) Private _pos As Integer Private ReadOnly _order As Integer Public Sub New(Optional Ascending As Boolean = True) _order = If(Ascending, 1, -1) End Sub Private Shared Function RegexSplit(ByVal s As String) As String() Return Regex.Split(s, "(\d+) Then", RegexOptions.IgnoreCase) End Function Private Shared Function GetEmptyStrings() As Predicate(Of String) Return Function(s) String.IsNullOrEmpty(s) End Function Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare Dim left As New List(Of String)(RegexSplit(x)) Dim right As New List(Of String)(RegexSplit(y)) left.RemoveAll(GetEmptyStrings()) right.RemoveAll(GetEmptyStrings()) _pos = 0 For Each x In left If y.Count > _pos Then If Not Decimal.TryParse(x, Nothing) AndAlso Not Decimal.TryParse(right(_pos), Nothing) Then Dim result As Integer = String.Compare(x, right(_pos), True) If result <> 0 Then Return result * _order Else _pos += 1 End If ElseIf Decimal.TryParse(x, Nothing) AndAlso Not Decimal.TryParse(right(_pos), Nothing) Then Return -1 * _order ElseIf Not Decimal.TryParse(x, Nothing) AndAlso Decimal.TryParse(right(_pos), Nothing) Then Return 1 * _order Else Dim result = Decimal.Compare(Decimal.Parse(x), Decimal.Parse(right(_pos))) If result = 0 Then _pos += 1 Else Return result * _order End If End If Else Return -1 * _order End If Next Return _order End Function End Class
问题排查
- 正则表达式错误:
RegexSplit方法中的正则"(\d+) Then"完全不符合需求,本意是拆分字符串中的数字与非数字部分,但该正则会匹配包含数字加Then的片段,导致无法正确拆分目标字符串。正确的拆分正则应为"(\d+)",可将字符串拆分为非数字段与数字段交替的数组。 - 循环逻辑混乱:使用
For Each x In left遍历左侧片段,同时用_pos索引右侧列表,当两侧片段数量不一致时,会出现索引不匹配或逻辑错误;循环结束后直接返回_order的逻辑错误,未考虑两侧片段长度不同的情况。 - 类型转换风险:直接使用
Decimal.Parse转换字符串,若数字超出Decimal范围会抛出异常,应使用TryParse安全处理转换。
修复后的实现
Imports System.Text.RegularExpressions Public Class NaturalComparer Implements IComparer(Of String) Private ReadOnly _order As Integer Public Sub New(Optional ascending As Boolean = True) _order = If(ascending, 1, -1) End Sub Private Shared Function RegexSplit(s As String) As String() ' 拆分数字与非数字部分,保留数字分组 Return Regex.Split(s, "(\d+)") End Function Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare ' 处理空值情况 If x Is Nothing And y Is Nothing Then Return 0 If x Is Nothing Then Return -1 * _order If y Is Nothing Then Return 1 * _order ' 获取非空片段列表 Dim leftParts = RegexSplit(x).Where(Function(part) Not String.IsNullOrEmpty(part)).ToList() Dim rightParts = RegexSplit(y).Where(Function(part) Not String.IsNullOrEmpty(part)).ToList() Dim minLength = Math.Min(leftParts.Count, rightParts.Count) ' 逐段比较 For i = 0 To minLength - 1 Dim leftPart = leftParts(i) Dim rightPart = rightParts(i) Dim leftIsNumber As Boolean, rightIsNumber As Boolean Dim leftNum As Decimal, rightNum As Decimal leftIsNumber = Decimal.TryParse(leftPart, leftNum) rightIsNumber = Decimal.TryParse(rightPart, rightNum) Select Case True Case leftIsNumber And rightIsNumber ' 数字按数值比较 Dim numCompare = Decimal.Compare(leftNum, rightNum) If numCompare <> 0 Then Return numCompare * _order Case Not leftIsNumber And Not rightIsNumber ' 非数字按字符串忽略大小写比较 Dim strCompare = String.Compare(leftPart, rightPart, StringComparison.OrdinalIgnoreCase) If strCompare <> 0 Then Return strCompare * _order Case Else ' 数字优先排在前面(升序) Return If(leftIsNumber, -1, 1) * _order End Select Next ' 前面片段均相同,长度更长的排在后面 Return leftParts.Count.CompareTo(rightParts.Count) * _order End Function End Class
修复后调用方式不变,即可实现预期的自然排序效果。
内容的提问来源于stack exchange,提问作者ExpertOnNothing
相关产品推荐
相关产品推荐

