I have some trouble implementing an IFormatProvider class that can parse strings that contain percentages into their numeric equivalent.
The problem is not in the parsing. Stackoverflow provides several solutions to parse a string that contains a percentages into a number.
- One solution involves creating a new number type called Percentage,
- Other solutions do not respect different cultures, or create a new TypeConverter
I'd rather not implement a new type. IMHO a percentage is not a new type, it is just a different way of displaying a number. A percentage sign is like the decimal point. In some cultures this is a dot, in other cultures this is a comma. This also does not lead to different types, only to different string formatting.
The functions Double.Parse(string, IformatProvider) (et al), provide possibilities to parse strings slightly different than the standard Double.Parse would do.
The problem I have is in the IFormatProvider. It is possible to order the Parse functions to use a special IFormatProvider. However I can't give this IFormatProvider any functionality to do special parsing. (By the way: Formatting to strings works almost fine).
MSDN describes the functionality of an IFormatProvider:
The IFormatProvider interface supplies an object that provides formatting information for formatting and parsing operations. ... Typical parsing methods are Parse and TryParse.
The default IFormatProvider does not Parse (meaning the function Parse, not the verb parse) strings that contains the percentage format as mentioned in System.Globalization.NumberFormatInfo
So I thought, maybe I could create my own IFormatProvider, that uses the solutions mentioned in the first lines of this question in such a way that it can be used to parse percentages according to the provided NumberFormatInfo, for every type that has Parse functions to parse strings into numbers.
Usage would be:
string txt = ... // might contain a percentage
// convert to double:
IFormatProvider percentFormatProvider = new PercentFormatProvider(...)
double d = Double.Parse(percentageTxt, percentFormatProvider)
What I've tried (that's the first what's being asked for)
So I created a simple IFormatProvider and checked what happened if I would call Double.Parse with the IFormatProvider
class PercentParseProvider : IFormatProvider
{
public object GetFormat(Type formatType)
{
...
}
}
Called using:
string txt = "0.25%";
IFormatProvider percentParseProvider = new PercentParseProvider();
double d = Double.Parse(txt, percentParseProvider);
And indeed, GetFormat is called, asking for an object of type NumberFormatInfo
Class NumberFormatInfo is sealed. So I can only return a standard NumberFormatInfo, if needed with changed values for properties. But I can't return a derived class that provides a special parsing method to parse percentages
String.Format(IFormatProvider, string, args)
I've noticed that using a format provider to do special formatting when converting to strings, works fine for String.Format. In that case GetFormat is called asking for an ICustomFormatter. All you have to do is return an object that implements ICustomFormatter and do the special formatting in ICustomFormatter.Format.
This works as expected. After returning the ICustomFormatter, its ICustomFormat.Format is called, where I can do the formatting I want.
Double.ToString(IFormatProvider)
However, when I used Double.ToString(string, IFormatProvider) I ran into the same problems as with Parse. In GetFormat a sealed NumberFormatInfo is asked for. If I return an ICustomFormatter, then the returned value is ignored and the default NumberFormatInfo is used.
Conclusion:
- String.Format(...) works fine with IFormatProvider, If desired you can do your own formatting
- Double.ToString(...) expects a sealed NumberFormatInfo, you can't do your own formatting
- Double.Parse expects a sealed NumberFormatInfo. No custom parsing allowed.
So: how to provide the parsing that MSDN promises in IFormatProvider?
IFormatProviders supply the data that an object will use in formatting itself. With them, you can only control what's defined in the
NumberFormatInfoandDateTimeFormatInfoobjects.While
ICustomFormatterallows the formatting of objects according to arbitrary rules, there is no equivalent parsing API.You could create such a culture-respecting parsing API that more-or-less mirrors
ToString(...)andParse(...)with a custom interface and extension methods. As Jeroen Mostert pointed out in this comment, though, the API is not exactly up to standards with .NET or C#'s newer features. One easy improvement that doesn't deviate from the syntax much is Generics support.You can't have extend classes with new static methods, though, so we unfortunately have to put
Parse<double>here onstringinstead of adding an overload toDouble.Parse().A reasonable thing to do at this junction would be to explore the other options you linked to... But to carry on, an
ICustomParser<>that would be relatively consistent withICustomFormattermight look like this:And some test cases:
It should also be noted that the MSDN documentation seems to contradict itself about how to implement
ICustomFormatter. It's Notes to Implementers section advises calling the appropriate implementation when you are called with something you can't format.However, the advice given in Custom formatting with ICustomFormatter" (and many of the MSDN examples) seems to recommend returning
nullwhen unable to format:So, take all this with a grain of salt. I don't recommend using any of this code, but it was an interesting exercise in understanding just how
CultureInfoandIFormatProviderwork.