首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >创建可以解析百分比的IFormatProvider

创建可以解析百分比的IFormatProvider
EN

Stack Overflow用户
提问于 2017-08-24 09:45:57
回答 1查看 592关注 0票数 2

我在实现一个IFormatProvider类时遇到了一些问题,这个类可以将包含百分比的字符串解析为它们的数值等价物。

问题不在于解析。Stackoverflow提供了几种解决方案,用于将包含百分比的字符串解析为数字。

我不想实现一种新的类型。IMHO a百分比不是一种新的类型,它只是显示一个数字的另一种方式。百分比符号就像小数点。在一些文化中,这是一个点,在另一些文化中,这是一个逗号。这也不会导致不同的类型,只会导致不同的字符串格式。

函数Double.Parse(string,IformatProvider) (等)提供了解析字符串的可能性,与标准的Double.Parse略有不同。

我遇到的问题是在IFormatProvider。可以命令Parse函数使用特殊的IFormatProvider。但是,我不能给这个IFormatProvider任何功能来进行特殊的解析。(顺便说一句:字符串的格式化工作得很好)。

描述了IFormatProvider的功能。

IFormatProvider接口提供了一个对象,该对象为格式化和解析操作提供格式化信息。..。典型的解析方法是Parse和TryParse。

默认的IFormatProvider不包含Parse (意思是函数Parse,而不是谓词解析)字符串,该字符串包含System.Globalization.NumberFormatInfo中提到的百分比格式

所以我想,也许我可以创建我自己的IFormatProvider,它使用这个问题的前几行中提到的解决方案,这样就可以根据提供的NumberFormatInfo来解析百分比,对于每种具有Parse函数的类型都可以将字符串解析为数字。

使用情况如下:

代码语言:javascript
复制
string txt = ...  // might contain a percentage
// convert to double:
IFormatProvider percentFormatProvider = new PercentFormatProvider(...)
double d = Double.Parse(percentageTxt, percentFormatProvider)

我尝试了什么(这是第一个被要求的)

因此,我创建了一个简单的IFormatProvider,并检查了如果我用IFormatProvider调用Double.Parse会发生什么

代码语言:javascript
复制
class PercentParseProvider : IFormatProvider
{
    public object GetFormat(Type formatType)
    {
        ...
    }
}

称为使用:

代码语言:javascript
复制
string txt = "0.25%";
IFormatProvider percentParseProvider = new PercentParseProvider();
double d = Double.Parse(txt, percentParseProvider);

实际上,GetFormat被调用,请求一个NumberFormatInfo类型的对象。

NumberFormatInfo类是密封的。因此,我只能返回一个标准的NumberFormatInfo,如果需要的话,可以更改属性的值。但是,我不能返回一个派生类,它提供了一个特殊的解析方法来解析百分比

String.Format(IFormatProvider,字符串,args)

我注意到,在转换为字符串时,使用格式提供程序进行特殊格式化,对于String.Format来说很好。在这种情况下,GetFormat被称为请求ICustomFormatter。您所要做的就是返回一个实现ICustomFormatter并在ICustomFormatter.Format中执行特殊格式的对象。

这如预期的那样起作用。返回ICustomFormatter后,将调用它的ICustomFormat.Format,在这里我可以执行所需的格式设置。

Double.ToString(IFormatProvider)

然而,当我使用Double.ToString(string,IFormatProvider)时,我遇到了与Parse相同的问题。在GetFormat中,需要一个密封的NumberFormatInfo。如果我返回一个ICustomFormatter,则将忽略返回的值,并使用默认的NumberFormatInfo

结论:

  • String.Format(.)可以使用IFormatProvider,如果需要,您可以自己格式化。
  • Double.ToString(.)期望一个密封的NumberFormatInfo,您不能自己格式化
  • Double.Parse期望一个密封的NumberFormatInfo。不允许进行自定义解析。

那么:如何提供MSDN在IFormatProvider中承诺的解析呢?

EN

回答 1

Stack Overflow用户

发布于 2021-05-12 23:59:36

IFormatProviders提供对象将用于格式化自身的数据。使用它们,您只能控制在NumberFormatInfoDateTimeFormatInfo对象中定义的内容。

虽然ICustomFormatter允许根据任意规则对对象进行格式化,但不存在等效的解析API。

您可以创建这样一个与文化有关的解析API,使其或多或少地将ToString(...)Parse(...)镜像为自定义接口和扩展方法。然而,正如Jeroen在这句话中所指出的那样,API并不完全符合.NET或C#更新特性的标准,一个简单的改进就是泛型支持。

代码语言:javascript
复制
public interface ICustomParser<T> where T : IFormattable {
    T Parse(string format, string text, IFormatProvider formatProvider);
}

public static class CustomParserExtensions
{
    public static T Parse<T>(this string self, string format, IFormatProvider formatProvider) where T : IFormattable
    {
        var parser = (formatProvider?.GetFormat(typeof(ICustomParser<T>)) as ICustomParser<T> ?? null);
        if (parser is null) // fallback to some other implementation. I'm not actually sure this is correct.
            return (T)Convert.ChangeType(self, typeof(T));

        var numberFormat = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ?? CultureInfo.CurrentCulture.NumberFormat;
        return parser.Parse(format, self, numberFormat);
    }
}

但是,您不能使用新的静态方法扩展类,因此不幸的是,我们不得不将Parse<double>放在string上,而不是向Double.Parse()添加重载。

在这个交界处做的一件合理的事情就是探索你联系到的其他选择.但要继续下去,一个与ICustomFormatter相对一致的ICustomFormatter可能如下所示:

代码语言:javascript
复制
// Using the same "implements ICustomFormat, IFormatProvider" pattern where we return ourselves
class PercentParser : ICustomParser<double>, IFormatProvider
{
    private NumberFormatInfo numberFormat;

    // If constructed with a specific culture, use that one instead of the Current thread's
    // If this were a Formatter, I think this would be the only way to provide a CultureInfo when invoked via String.Format() (aside from altering the thread's CurrentCulture)
    public PercentParser(IFormatProvider culture)
    {
        numberFormat = culture?.NumberFormat;
    }
    
    public object GetFormat(Type formatType)
    {
        if (typeof(ICustomParser<double>) == formatType) return this;
        if (typeof(NumberFormatInfo) == formatType) return numberFormat;
        return null;
    }
    
    public double Parse(string format, string text, IFormatProvider formatProvider)
    {
        var numberFmt = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo ?? this.numberFormat ?? CultureInfo.CurrentCulture.NumberFormat;

        // This and TrimPercentDetails(string, out int) are left as an exercise to the reader. It would be very easy to provide a subtly incorrect solution.
        if (IKnowHowToParse(format))
        {
            value = TrimPercentDetails(value, out int numberNegativePattern);

            // Now that we've handled the percentage sign and positive/negative patterns, we can let double.Parse handle the rest.
            // But since it doesn't know that it's formatted as a percentage, so we have to lie to it a little bit about the NumberFormat:
            numberFmt = (NumberFormatInfo)numberFmt.Clone(); // make a writable copy

            numberFmt.NumberDecimalDigits = numberFmt.PercentDecimalDigits;
            numberFmt.NumberDecimalSeparator = numberFmt.PercentDecimalSeparator;
            numberFmt.NumberGroupSeparator = numberFmt.PercentGroupSeparator;
            numberFmt.NumberGroupSizes = numberFmt.PercentGroupSizes;
            // Important note! These values mean different things from percentNegativePattern. See the Reference Documentation's Remarks for both for valid values and their interpretations!
            numberFmt.NumberNegativePattern = numberNegativePattern; // and you thought `object GetFormat(Type)` was bad!

        }
        
        return double.Parse(value, numberFmt) / 100;
    }
}

还有一些测试用例:

代码语言:javascript
复制
Assert(.1234 == "12.34%".Parse<double>("p", new PercentParser(CultureInfo.InvariantCulture.NumberFormat));

// Start with a known culture and change it all up:
var numberFmt = (NumberFormatInfo)CultureInfo.InvariantCulture.NumberFormat.Clone();
numberFmt.PercentDemicalDigits = 4;
numberFmt.PercentDecimalSeparator = "~a";
numberFmt.PercentGroupSeparator = " & ";
numberFmt.PercentGroupSizes = new int[] { 4, 3 };
numberFmt.PercentSymbol = "percent";
numberFmt.NegativeSign = "¬!-";
numberFmt.PercentNegativePattern = 8;
numberFmt.PercentPositivePattern = 3;

// ensure our number will survive a round-trip
double d = double.Parse((-123456789.1011121314 * 100).ToString("R", CultureInfo.InvariantCulture));
var formatted = d.ToString("p", numberFmt);
double parsed = formatted.Parse<double>("p", new PercentParser(numberFmt))
// Some precision loss due to rounding with NumberFormatInfo.PercentDigits, above, so convert back again to verify. This may not be entirely correct
Assert(formatted == parsed.ToString("p", numberFmt);

还应该注意的是,MSDN文档在如何实现ICustomFormatter方面似乎自相矛盾。它的https://learn.microsoft.com/en-us/dotnet/api/system.icustomformatter?view=net-5.0#notes-to-implementers部分建议,当您使用无法格式化的内容调用时,应该调用适当的实现。

扩展实现是为已经具有格式设置支持的类型提供自定义格式设置的实现。例如,您可以定义一个在特定数字之间用连字符格式化整型的CustomerNumberFormatter。在这种情况下,您的实现应该包括以下内容:

  • 扩展对象格式的格式字符串的定义。这些格式字符串是必需的,但它们不能与类型的现有格式字符串冲突。例如,如果要扩展Int32类型的格式设置,则不应该实现"C“、"D”、"E“、"F”和"G“格式说明符等。
  • 传递给您的格式(String、Object、IFormatProvider)方法的对象类型的测试是扩展支持其格式的类型。如果不是,则调用对象的IFormattable实现(如果存在),或者调用对象的无参数ToString()方法(如果不存在)。您应该准备好处理这些方法调用可能引发的任何异常。
  • 处理扩展支持的任何格式字符串的代码。
  • 处理扩展不支持的任何格式字符串的代码。这些应该传递到类型的IFormattable实现。您应该准备好处理这些方法调用可能引发的任何异常。

但是,在https://learn.microsoft.com/en-us/dotnet/standard/base-types/formatting-types#custom-formatting-with-icustomformatter中给出的建议(以及许多MSDN示例)似乎建议在无法格式化时返回null

该方法返回要格式化的对象的自定义格式化字符串表示形式。如果方法不能格式化对象,它应该返回一个null。

所以,用一点盐把这些都拿走。我不建议使用这些代码,但这是理解CultureInfoIFormatProvider如何工作的一个有趣的练习。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/45858276

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档