ICustomFormatter返回null时string.Format返回空串的解决方法咨询
我太懂这个坑了!你遇到的情况完全是string.Format的内置行为搞的鬼——官方文档写得明明白白:
如果参数值为null,格式项将被替换为String.Empty。
更气人的是,这个null替换逻辑是在调用你的ICustomFormatter.Format方法之前就执行了,所以哪怕你在Formatter里写死返回null,也根本轮不到它生效,因为传入你方法的arg已经不是null了(或者说,string.Format直接跳过了调用,直接替换成空字符串了)。
下面给你几个可行的解决方案,按需选择:
方案1:直接调用自定义Formatter,绕开string.Format
既然string.Format要搞小动作,那我们直接跳过它,自己调用Formatter的Format方法就行:
var myFormatter = new MyCustomFormatProvider(); // 直接调用Format方法,参数传null进去 var result = myFormatter.Format("some-custom", null, myFormatter); // 这里result就是你想要的null,不会被替换成空字符串
这个方案最简单,如果你只是偶尔需要处理null场景,直接用这个最省心。
方案2:用包装类标记null值(必须用string.Format时)
如果你的业务场景必须依赖string.Format的格式化语法(比如多个参数拼接),可以给null做个"标记包装",让你的Formatter能识别它:
首先定义一个极简的包装类:
// 只是用来标记这是一个需要返回null的特殊值,不需要任何逻辑 public class NullMarker { }
然后修改你的ICustomFormatter实现:
case "some-custom": // 先判断是不是我们的标记类 if (arg is NullMarker) { return null; } // 原来的其他类型处理逻辑保持不变 else if (arg is double d) { return Math.Round(d, _numberFormatter.NumberDecimalDigits, MidpointRounding.AwayFromZero) .ToString(CustomNumericFormat, this); } else if (arg is decimal dec) { return Math.Round(dec, _numberFormatter.NumberDecimalDigits, MidpointRounding.AwayFromZero) .ToString(CustomNumericFormat, this); } // 其他逻辑...
调用的时候把null换成这个标记类:
string.Format(MyCustomFormatProvider, "{0:some-custom}", new NullMarker());
这样string.Format不会把NullMarker当成null处理,会老老实实把它传给你的Formatter,你就能返回null了。
方案3:自己实现一个替代string.Format的方法
如果需要频繁处理这种场景,不如自己写一个自定义的Format方法,完全掌控null的处理逻辑:
public static string CustomFormat(IFormatProvider provider, string format, params object[] args) { if (format == null) throw new ArgumentNullException(nameof(format)); if (args == null) throw new ArgumentNullException(nameof(args)); var customFormatter = provider?.GetFormat(typeof(ICustomFormatter)) as ICustomFormatter; var resultBuilder = new StringBuilder(); // 用正则匹配格式项,比如 {0:some-custom} var formatRegex = new Regex(@"\{(\d+)(:[^}]+)?\}"); int lastPosition = 0; foreach (Match match in formatRegex.Matches(format)) { // 拼接格式项之前的普通文本 resultBuilder.Append(format.Substring(lastPosition, match.Index - lastPosition)); lastPosition = match.Index + match.Length; // 解析格式项里的参数索引和格式字符串 int argIndex = int.Parse(match.Groups[1].Value); if (argIndex >= args.Length) { // 参数索引越界,保留原格式项 resultBuilder.Append(match.Value); continue; } object argValue = args[argIndex]; string formatSpec = match.Groups[2].Success ? match.Groups[2].Value.Substring(1) : null; string formattedText; if (customFormatter != null) { // 直接调用自定义Formatter,不做null替换 formattedText = customFormatter.Format(formatSpec, argValue, provider); } else { // 没有自定义Formatter时的默认处理 formattedText = argValue?.ToString() ?? string.Empty; } // 这里可以自定义null的处理:如果formattedText是null,就不添加任何内容 if (formattedText != null) { resultBuilder.Append(formattedText); } } // 拼接最后一段普通文本 resultBuilder.Append(format.Substring(lastPosition)); return resultBuilder.ToString(); }
然后用这个方法替代string.Format,这样当你的Formatter返回null时,对应的格式项位置就不会被替换成空字符串,完全按照你的逻辑来。
内容的提问来源于stack exchange,提问作者S1r-Lanzelot

