You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

C#:自定义IFormatProvider与ICustomFormatter未被调用问题

问题分析与解决方案

这事儿的核心是你没搞懂.NET格式化系统的调用顺序!咱们一步步拆解:

为什么你的代码没触发自定义Format

当你调用string.Format(provider, "123: {0:X}", 123)时,.NET的格式化流程是这样的:

  1. 首先会尝试调用provider.GetFormat(typeof(ICustomFormatter)),看看有没有自定义格式化器——但你原来的LoggingFormatProviderGetFormat根本没处理这个类型请求,直接跳过了,所以系统根本不知道你有自定义格式化逻辑。
  2. 接着因为你指定了格式说明符:X,系统会去请求NumberFormatInfo,你返回的MyNumberFormatter(或者后来的this)是ICustomFormatter类型,不是NumberFormatInfo!.NET会默默忽略这个类型不匹配的错误,转而使用默认的数字格式化器,所以最终输出了十六进制的7B

修正后的正确实现

要让自定义格式化生效,必须让IFormatProvider在系统请求ICustomFormatter时返回你的格式化实例,同时在Format方法里处理格式说明符。这里直接让LoggingFormatProvider同时实现两个接口,代码更简洁:

using System;
using System.Globalization;

internal class LoggingFormatProvider : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        // 优先处理ICustomFormatter请求,这是string.Format的第一优先级
        if (formatType == typeof(ICustomFormatter))
        {
            Console.WriteLine("formatType == typeof(ICustomFormatter): True");
            return this;
        }
        // 处理NumberFormatInfo请求,这里可以返回自定义或默认的格式信息
        else if (formatType == typeof(NumberFormatInfo))
        {
            Console.WriteLine("formatType == typeof(NumberFormatInfo): True");
            return CultureInfo.CurrentCulture.NumberFormat;
        }
        else
        {
            Console.WriteLine($"formatType is {formatType.Name}: False");
            return null;
        }
    }

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        string result;
        
        // 针对整数类型处理格式说明符
        if (arg is int number)
        {
            if (!string.IsNullOrEmpty(format))
            {
                // 先按指定格式(比如:X十六进制)转换,再替换数字/字母
                string formatted = number.ToString(format, formatProvider);
                result = formatted.Replace("1", "One")
                                 .Replace("2", "Two")
                                 .Replace("3", "Three")
                                 .Replace("4", "Four")
                                 .Replace("5", "Five")
                                 .Replace("6", "Six")
                                 .Replace("7", "Seven")
                                 .Replace("8", "Eight")
                                 .Replace("9", "Nine")
                                 .Replace("0", "Zero")
                                 .Replace("A", "Ten")
                                 .Replace("B", "Eleven");
            }
            else
            {
                // 无格式说明符时直接替换数字
                result = number.ToString().Replace("1", "One")
                                          .Replace("2", "Two")
                                          .Replace("3", "Three")
                                          .Replace("4", "Four")
                                          .Replace("5", "Five")
                                          .Replace("6", "Six")
                                          .Replace("7", "Seven")
                                          .Replace("8", "Eight")
                                          .Replace("9", "Nine")
                                          .Replace("0", "Zero");
            }
        }
        else
        {
            // 其他类型用默认格式化兜底
            try
            {
                result = string.Format(formatProvider, $"{{0:{format}}}", arg);
            }
            catch
            {
                result = arg.ToString();
            }
        }

        Console.WriteLine("Returning: {0}", result);
        return result;
    }
}

调用代码不变,运行结果符合预期

你的Main方法不需要修改,运行后会输出:

Using string.Format()
formatType == typeof(ICustomFormatter): True
Returning: SevenEleven
123: SevenEleven
 Returned: 123: SevenEleven

关键要点总结

  • string.Format优先找ICustomFormatter,所以GetFormat必须先处理这个类型请求。
  • 自定义Format方法要兼容格式说明符,不能直接忽略掉:X这类参数。
  • 返回的格式化器类型必须和请求的类型匹配,不能用ICustomFormatter冒充NumberFormatInfo

内容的提问来源于stack exchange,提问作者BanksySan

火山引擎 最新活动