Visual Studio对文本行应用哪种排序方式?
在我们的源代码库里,存在一种特定类型的文本文件,它由若干行文本组成,且这几行始终需要保持有序。幸运的是,从Visual Studio 2022开始,存在一个 Edit -> Advanced -> Sort Lines 命令,因此团队中任何修改该文件的开发人员在提交前都可以使用这个命令对文件进行排序。
现在,人类经常会犯错,包括忘记做某事,所以偶尔会有人修改该文件后直接提交,而没有先对其进行排序。为此,我打算添加一个测试,意在确保文件中的行确实已经排好序。
不幸的是,测试总是失败。即使我在Visual Studio里刚刚把它排序,测试仍然显示未排序,这是因为Visual Studio的排序方式与我的测试理解排序的方式之间存在不匹配。
具体而言,Visual Studio和我的测试在字母、点号、破折号、反斜杠、下划线等字符之间的排序顺序似乎存在分歧。 (注:前导空格不在有问题的字符清单中;我的文件也没有前导空格。)
在C#中,判断字符串是否有序涉及执行某种字符串比较,而以下几种比较字符串的方式似乎都无法与Visual Studio的排序方式相匹配:
string.Compare(s1, s2, StringComparison.Ordinal)
string.Compare(s1, s2, StringComparison.CurrentCulture)
string.Compare(s1, s2, CultureInfo.InvariantCulture, CompareOptions.StringSort)
string.Compare(s1, s2, CultureInfo.InvariantCulture, CompareOptions.IgnoreSymbols)
string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreSymbols)
我目前正在尝试编写一个自定义的字符串比较函数,力图模拟Visual Studio对行的排序方式;但本质上是在逆向工程Visual Studio使用的排序算法,这个过程主要靠试验和错误,效率并不高。
这就引出一个问题:当我们调用 Edit -> Advanced -> Sort Lines 命令时,Visual Studio对文本行到底应用了哪种排序?
换句话说,如何在C#中以编程方式精确复现那种排序?
更新
Google Gemini建议从 shlwapi.dll 使用P/Invoke调用 StrCmpLogicalW(string x, string y),但我希望找到一个不涉及P/Invoke的解决方案。
解决方案
你的环境与标准:
- 代码托管/CI/CD使用GitLib,这意味着无法使用GitHub的 Action
- 代码没有前导空格
目前团队的常规做法是手动使用VS的 Sort Lines 功能,但人工操作并不可靠,总有人在提交代码前忘记执行。
下面给出一个看起来很曲折但肯定能工作的方法,但需要改变团队的操作步骤。
- 假设需要处理的源代码文件名为 source.txt
- 先将它复制到一个临时文件夹中。稍后我们会用到这个副本。
- 在VS项目中,使用它的
Add->New Item...->Text Template功能来创建一个新的模板文件,该模板的基名必须与源文件相同,因此应命名为 source.tt - 将模板文件的内容编辑如下:
<#@ template language="C#" #>
<#@ output extension=".txt" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#
string source =
"""
foo
bar
and more
""";
foreach (string line in source.Split(new[] { "\r\n" }, StringSplitOptions.None)
.OrderBy(x => x))
{
#>
<#= line #>
<#
}
#>
- 编辑临时文件夹中先前备份的 source.txt 文件,复制其内容并粘贴到原始字符串字面量
source中,代替我们foo / bar / and more行的位置。由此,这个source变量实际上就是你们团队原始“源代码”应放置的地方。 - 团队的操作需要改变:从现在起请不要直接修改 source.txt。相反,当需要修改时,打开VS,在这个 source.tt 文件中编辑
source变量的字面字符串。
说明
- 每当使用Visual Studio修改并保存一个
.tt文件时,T4引擎会自动执行模板文件中<# ... #>块里的代码。 - 模板内容位于
<# ... #>块之外,T4引擎通常会将其直接复制到输出文件中,该输出文件的基名与模板文件相同,但文件扩展名将按照<#@ output extension=".txt" #>指令指定。 - 由于T4引擎总是自动覆盖原始源文件,因此在此过程开始前,我们需要先在临时文件夹中备份一份,以避免在此过程被覆盖。
- 指令
<#@ template language="C#" #>选择我们在代码中使用的语言,备选的是VB。 - 在C#代码中,恰当的缩进通常是推荐的,例如一个
foreach循环。然而,由于我们不想在输出行前面多出空格,请务必在<#= line #>之前不要添加空格。 <#=expr#>是我们可以编写任意C#表达式并将其求值结果写入输出文件的确切位置的方式。就本示例而言,一个简单的line表达式就足够了。- 为简洁起见,代码使用
OrderBy(),它是一个LINQ的扩展方法。这也是为何需要<#@ assembly name="System.Core" #>与<#@ import namespace="System.Linq" #>指令。如果你想要更改排序算法,这是可行的,取决于你的判断,可能需要改用不同的指令来处理程序集/命名空间。
参考资料
- Writing a T4 Text Template: (微软官方文档)
https://learn.microsoft.com/en-us/visualstudio/modeling/writing-a-t4-text-template?view=visualstudio
- 另一个可以用T4解决的案例: