This class has the responsibility of parsing a string to one of the primitive types: int
, double
, float
, decimal
...
While creating it, I have noticed that it'll be terribly ugly.
It basically should perform a TryParse
of each primitive type and return the first that returns true, along with the parsed value.
public static class PrimitiveParser
{
public static bool TryParse(Type targetType, string sourceValue, out object result)
{
result = null;
if (targetType == typeof(int))
{
int intResult;
var parseResult = int.TryParse(sourceValue, out intResult);
{
result = intResult;
return parseResult;
}
}
if (targetType == typeof(double))
{
double doubleResult;
var parseResult = double.TryParse(sourceValue, out doubleResult);
{
result = doubleResult;
return parseResult;
}
}
if (targetType == typeof(float))
{
float floatResult;
var parseResult = float.TryParse(sourceValue, out floatResult);
{
result = floatResult;
return parseResult;
}
}
/// ...
return false;
}
}
-
4\$\begingroup\$ As I am reading this it does not perform a TryParse of each primitive type and return the first that returns true. \$\endgroup\$paparazzo– paparazzo2016年11月30日 21:50:57 +00:00Commented Nov 30, 2016 at 21:50
2 Answers 2
I'd prefer to use a dictionary for this.
private delegate bool TryParseDelegate<T>(string text, out T value);
private static readonly Dictionary<Type, object> TryParsers = new Dictionary<Type, object>
{
{ typeof(int), (TryParseDelegate<int>)int.TryParse },
{ typeof(float), (TryParseDelegate<float>)float.TryParse },
{ typeof(double), (TryParseDelegate<double>)double.TryParse }
};
The TryParse
method:
public static bool TryParse<T>(string value, out T returnValue)
{
// Check if we have a parse method for T in the dictionary.
object parserObj;
if (TryParsers.TryGetValue(typeof(T), out parserObj))
{
TryParseDelegate<T> parser = (TryParseDelegate<T>)parserObj;
return parser(value, out returnValue);
}
// If no, fallback to IConvertible.
if (typeof(IConvertible).IsAssignableFrom(typeof(T)))
{
try
{
returnValue = (T)Convert.ChangeType(value, typeof(T));
return true;
}
catch { }
}
// No luck.
returnValue = default(T);
return false;
}
This approach support exensibility.
For example, imagine we have a class A
:
public class A
{
public static readonly A Zero = new A("Zero");
public static readonly A One = new A("One");
public readonly string Text;
private A(string text)
{
Text = text;
}
public static bool TryParse(string text, out A value)
{
switch (text.ToLower())
{
case "zero":
value = Zero;
break;
case "one":
value = One;
break;
default:
value = null;
return false;
}
return true;
}
public override string ToString()
{
return Text;
}
}
Then the only thing you have to do is to append an item to the dictionary:
{ typeof(A), (TryParseDelegate<A>)A.TryParse }
Let's test it:
int i;
double d;
A a;
if (TryParse("123", out i))
Console.WriteLine($"i={i}"); // Prints i=123
if (TryParse("123.456", out d))
Console.WriteLine($"d={d}"); // Prints d=123.456
if (TryParse("ONE", out a))
Console.WriteLine($"a={a}"); // Prints a=One
You may be over thinking this, all the types have a common IConvertible
implementation and you can use Convert.ChangeType
to parse from a string.
public static bool TryParse<T>(string value, out T returnValue)
{
returnValue = default(T);
try
{
returnValue = (T)Convert.ChangeType(value, typeof(T));
return true;
}
catch { }
return false;
}
Which you can call like this:
float output = 0.0f;
var val = TryParse("19.2", out output);
//val == true, output = 19.2
val = TryParse("puppies", out output);
//val == false, output = 0.0f (default(T))
As an added bonus, this works with any type that supports IConvertible
. See Convert.ChangeType
for more information.
The only thing I don't like about this approach is that it uses a try
/catch
to determine if the conversion is possible or not. This is very performant when the types are convertible but does suffer a performance penalty for the exception when the type is not convertible.
-
1\$\begingroup\$ The interface definitely lacks a
CanConvert
method. \$\endgroup\$t3chb0t– t3chb0t2016年11月30日 16:54:07 +00:00Commented Nov 30, 2016 at 16:54 -
2\$\begingroup\$ @t3chb0t I agree, although the designers probably recognized that
CanConvert
would have to do pretty much the same as above (attempt the conversion and fail) since it wouldn't be any less time-complex. I think a nicer addition would be theTryConvert
which returns true/false and the value like all theTryParse
methods on the primitive types. It may be possible to usedynamic
in this situation and just callTryParse
directly if the type is primitive (and not a pointer), but that may cost extra and I'm not sure how you'd call that without reflection (I don't think you can(dynamic)T
. \$\endgroup\$Ron Beyer– Ron Beyer2016年11月30日 16:57:42 +00:00Commented Nov 30, 2016 at 16:57