Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic wrapper for TryParse() in C#

Tags:

c#

generics

I'm using the following methods to provide quick, inline access to the TryParse() method of various Type classes. Basically I want to be able to parse a string coming from a web service if possible or return a default value if not.

private Int64 Int64Parse(string value) {
    Int64 result;
    if (!Int64.TryParse(value, out result)) { return default(Int64); }
    return result;
}

private DateTime DateTimeParse(string value) {
    DateTime result;
    if (!DateTime.TryParse(value, out result)) { return default(DateTime); }
    return result;
}

private Decimal DecimalParse(string value) {
    Decimal result;
    if (!Decimal.TryParse(value, out result)) { return default(Decimal); }
    return result;
}

These are extremely repetitive, suggesting, to me, that there may be a way to wrap them into a single generic method.

I'm stuck at the following but not sure how to proceed or how to search for how to proceed.

private T ParseString<T>(string value) {
    T result;
    if (!T.TryParse(value, out result)) { return default(T); }
    return result;
}

Any help would be appreciated. Thanks.

==Edit== To add some context. This is for a listener receiving postbacks from a specific credit card billing company. I'm not doing validation at this step because that's being done in the business rules steps later. For example, I don't care if bank_batch_number comes in as an int, string or freeze-dried rodent; I'm not going to halt with an exception if I can't cleanly log a field I don't use. I do care that ext_product_id exists in our DB and has a price matching currency_amount_settled in the message; if that test fails then the transaction is put on hold, a warning is logged, and our CS staff and myself will be alerted.

The culture thing mentioned below is sage advice though.

like image 719
SnowCrash Avatar asked Jan 29 '13 19:01

SnowCrash


People also ask

What is TryParse?

TryParse(String, Int32) Converts the string representation of a number to its 32-bit signed integer equivalent. A return value indicates whether the conversion succeeded.

What does TryParse return if successful?

public static bool TryParse (string value, out bool result); Parameters: value: It is a string containing the value to convert. result: When this method returns, if the conversion succeeded, contains true if value is equal to TrueString or false if value is equal to FalseString.

Can you TryParse a string?

TryParse(String, NumberStyles, IFormatProvider, Single) Converts the string representation of a number in a specified style and culture-specific format to its single-precision floating-point number equivalent. A return value indicates whether the conversion succeeded or failed.


2 Answers

No, the methods are basically entirely separate - the compiler doesn't know that DateTime.TryParse is similar in any way to Int64.TryParse etc.

You could create a Dictionary<Type, Delegate> to map from the target type to the method, and then write something like:

private delegate bool Parser<T>(string value, out T result);

private T Parse<T>(string value) where T : struct
{
    // TODO: Validate that typeof(T) is in the dictionary
    Parser<T> parser = (Parser<T>) parsers[typeof(T)];
    T result;
    parser(value, out result);
    return result;
}

You can populate the dictionary like this:

static readonly Dictionary<Type, Delegate> Parsers = CreateParsers();    

static Dictionary<Type, Delegate> CreateParsers()
{
    var parsers = new Dictionary<Type, Delegate>();
    AddParser<DateTime>(parsers, DateTime.TryParse);
    AddParser<Int64>(parsers, Int64.TryParse);
    return parsers;
}

static void AddParser<T>(Dictionary<Type, Delegate> parsers, Parser<T> parser)
{
    parsers[typeof(T)] = parser;
}

Note that the TryParse pattern states that the value of the out parameter will be the default value for that type anyway, so you don't need your conditional logic. That means even your repetitive methods can become simpler:

private static Decimal DecimalParse(string value) {
    Decimal result;
    Decimal.TryParse(value, out result);
    return result;
}

As an aside, note that by default the TryParse pattern will use the thread's current culture. If this is being used to parse incoming data from a web service, I'd strongly recommend that you use the invariant culture instead. (Personally I wouldn't silently ignore bad data either, but I assume that's deliberate.)

like image 111
Jon Skeet Avatar answered Sep 24 '22 22:09

Jon Skeet


Why not just use a simple extension method?

Jon Skeet's answer about just using the default result from the various TryParse methods is good. There is still a nice aspect of the extension methods, though. If you are doing this a lot, you can accomplish the same thing in the calling code (plus optionally specifying an explicit default) in one line of code rather than three.

-- EDIT -- I do realize that in my original answer I basically just provided a slightly different way of doing the same thing the author was already doing. I caught this earlier today when I was real busy, thought the delegate and custom parser stuff looked like it might be a bit much, then cranked out an answer without really taking the time to completely understand what the question was. Sorry.

How about the following, which uses an (overloaded) extension method and reflection? Refer to https://stackoverflow.com/a/4740544/618649

Caveat Emptor: my example does not account for you trying to convert types that do not have a TryParse method. There should be some exception handling around the GetMethod call, and so on.

/* The examples generates this output when run:

0
432123
-1
1/1/0001 12:00:00 AM
1/1/1970 12:00:00 AM
1/30/2013 12:00:00 PM
-1
12342.3233443

*/


class Program
    {
    static void Main ( string[] args )
        {
        Debug.WriteLine( "blah".Parse<Int64>() );
        Debug.WriteLine( "432123".Parse<long>() );
        Debug.WriteLine( "123904810293841209384".Parse<long>( -1 ) );

        Debug.WriteLine( "this is not a DateTime value".Parse<DateTime>() );
        Debug.WriteLine( "this is not a DateTime value".Parse<DateTime>( "jan 1, 1970 0:00:00".Convert<DateTime>() ) );
        Debug.WriteLine( "2013/01/30 12:00:00".Parse<DateTime>() );

        Debug.WriteLine( "this is not a decimal value".Parse<decimal>( -1 ) );
        Debug.WriteLine( "12342.3233443".Parse<decimal>() );
        }
    }

static public class Extensions
    {
    static private Dictionary<Type,MethodInfo> s_methods = new Dictionary<Type, MethodInfo>();

    static public T Parse<T> ( this string value ) where T : struct
        {
        return value.Parse<T>( default( T ) );
        }

    static public T Parse<T> ( this string value, T defaultValue ) where T : struct
        {
        // *EDITED* to cache the Reflection lookup--NOT thread safe
        MethodInfo m = null;
        if ( s_methods.ContainsKey( typeof( T ) ) )
            {
            m = s_methods[ typeof( T ) ];
            }
        else
            {
            m = typeof( T ).GetMethod(
                 "TryParse"
                 , BindingFlags.Public | BindingFlags.Static
                 , Type.DefaultBinder
                 , new[] { typeof( string ), typeof( T ).MakeByRefType() }
                 , null
                 );
            s_methods.Add( typeof( T ), m );
            }

        var args = new object[] { value, null };
        if( (bool)m.Invoke( null, args ))
            {
            return (T) args[ 1 ];
            }
        return defaultValue;
        }
    }
like image 43
Craig Tullis Avatar answered Sep 25 '22 22:09

Craig Tullis