背景
不久前,迁移了一个 framework 项目到 .net core 上面,部署也从 Windows 的 IIS 到 linux 的容器化。
期间遇到了一个关于中文字符串排序的问题,在这里记录一下。
复现与处理
下面这段代码就是出现问题的代码。
var list = new List<string>
{
"阿莫西林", "阿司匹林", "阿卡波糖"
};
list.Sort();
foreach (var item in list)
{
Console.WriteLine(item);
}
大家觉得,排序后的结果会是怎么样的呢?
这个排序结果取决于当前环境的 CultureInfo,因为 list.Sort()
最后会调用 Array.Sort<T>(Array keys, Array? items, int index, int length, IComparer? comparer)
,没有显式的传递 comparer
参数,这个时候就会用到 Comparer.Default
,这个 default 的构造函数就传入了 CultureInfo.CurrentCulture。
大体调用逻辑可以参考下面的 github 链接
https://github.com/dotnet/runtime/blob/dfe1076090adad6990747e6abed8bf6699371877/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs#L1046
https://github.com/dotnet/runtime/blob/dfe1076090adad6990747e6abed8bf6699371877/src/libraries/System.Private.CoreLib/src/System/Array.cs#L1817
https://github.com/dotnet/runtime/blob/dfe1076090adad6990747e6abed8bf6699371877/src/libraries/System.Private.CoreLib/src/System/Array.cs#L1850
https://github.com/dotnet/runtime/blob/dfe1076090adad6990747e6abed8bf6699371877/src/libraries/System.Private.CoreLib/src/System/Collections/Comparer.cs#L21
知道这个的话,就可以来验证一下了。
Console.WriteLine("Sort by default culture , {0}", Thread.CurrentThread.CurrentCulture.Name);
list.Sort();
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine("");
Console.WriteLine("Sort by culture , en-US");
list.Sort(StringComparer.Create(new System.Globalization.CultureInfo("en-US"), true));
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine("");
Console.WriteLine("Sort by culture , zh-Hans-CN");
list.Sort(StringComparer.Create(new System.Globalization.CultureInfo("zh-Hans-CN"), true));
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine("");
Console.WriteLine($"CurrentCulture Name = {System.Globalization.CultureInfo.CurrentCulture.Name}");
本地调试输出如下:
基于 mcr.microsoft.com/dotnet/runtime:6.0-bullseye-slim
镜像,
docker build -t sort:v1 -f .\Dockerfile .
docker run --rm sort:v1
放到容器下面输出如下:
可以看到,默认的是为空的,也就是没有任何指定。
由于 bullseye-slim 镜像已经包含了 icu 相关的库,所以可以直接指定 LC_ALL 或 LANG 为 zh_CN 即可。
docker run --rm -e LC_ALL=zh_CN sort:v1
# 或
docker run --rm -e LANG=zh_CN sort:v1
这个时候的输出就是和本地调试一致的了。
所以最后的解决方案是 添加多一个环境变量 来解决这个问题。
如果是用 alpine 镜像的话,要注意安装 icu 相关的库。