6

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.

SnowCrash
  • 82
  • 1
  • 6

4 Answers4

8

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.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I'll give this a try and see how it goes. Silently ignoring bad data at this step is intentional as this step is after raw logging and before verification against business rules. Also, since not every field is present in every request, several will actually be going into the parser with an empty string via something like [invoice.ReferenceInvoiceId = IntParse( Params.FirstOrDefault(p => p.Key == "ref_inv").Value);] – SnowCrash Jan 29 '13 at 20:23
  • Any thoughts on the cost of the Dictionary lookup vs other solutions? – Jon Feb 27 '17 at 11:54
  • @Jon: The same as my normal thoughts on performance: measure alternative approaches in the context you're actually interested in. A cost which is insignificant in one context may be a killer in a different context. – Jon Skeet Feb 27 '17 at 12:02
2
public delegate bool TryParseDelegate<T>(string str, out T value);

public static T ParseOrDefault<T>(string str, TryParseDelegate<T> parse)
{
    T value;
    return parse(str, out value) ? value : default(T);
}

You can call it like:

long l = ParseOrDefault<long>("12345", long.TryParse);
Lee
  • 142,018
  • 20
  • 234
  • 287
2

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;
        }
    }
Community
  • 1
  • 1
Craig Tullis
  • 9,939
  • 2
  • 21
  • 21
  • Although--what Jon Skeet said about the culture settings and ignoring bad data... – Craig Tullis Jan 30 '13 at 20:44
  • I edited my original answer, so that it now actually answers the question that was being asked rather than just providing a different way to do the same thing that was already being done. Mea culpa. Good luck! – Craig Tullis Jan 31 '13 at 07:08
  • This looks to be nearly exactly what I was looking for. Using the extension method method fits with my current "lots o' linq" style better than the way I was using statically typed methods. – SnowCrash Jan 31 '13 at 16:01
  • 2
    @SnowCrash: Do you *really* want to be using reflection on every single conversion? That doesn't sound like a great idea to me. Obviously you can still change my approach to use an extension method as the public API - but I would personally stick with the "register a bunch of delegates" approach for the actual conversion. – Jon Skeet Jan 31 '13 at 16:11
  • That would definitely work, Jon. I do NOT begrudge your delegates! I readily admit that I really do like extension methods, though. :-) I suppose you'd have to test the performance to see what the penalty is. It might be a case of the old 80/20 rule. ASP.NET MVC uses reflection on every page request to implement its action-by-convention mechanism and seems to do okay. – Craig Tullis Jan 31 '13 at 16:53
  • You could cache the result of the Reflection lookup. I modified my example to show this possibility. I also added generic type constraints, as per Jon Skeet's example, which I should have followed in the first place. Regarding the cost of C# reflection, how much worse is it, really, than dynamic invocation in other common languages where late binding/dynamic invocation is the _only_ way things work? I'm thinking of Objective-C or Ruby? I don't necessarily have that answer, but as always, test, test, test... :-) – Craig Tullis Jan 31 '13 at 17:34
  • I was actually already thinking of adding a dictionary cache. I'll do some benchmarks to see if it's needed. Performance isn't the top concern here since it's not going to be user interactive. If the server can make it through this listener in less than 60 seconds, our billing vendor won't complain. – SnowCrash Jan 31 '13 at 20:09
  • You could cache the MethodInfo instances for the most common types (long, decimal, DateTime, int, whatever) in the Extensions class constructor. The static class constructor is guaranteed by .NET to be thread safe. You would incur a tiny bit of startup penalty the first time the extension method is called. – Craig Tullis Jan 31 '13 at 22:05
0

This is a method I sometimes use. If performance is a major concern then this is not the way to go, as the try-catch block and the call to ChangeType are going to slow you down a good bit more than a type-specific TryParse.

public static bool TryFromString<T>(string value, out T convertedValue)
{
    Type t = typeof(T);
    convertedValue = default(T);
    if (t.Name == "Nullable`1")
        t = System.Nullable.GetUnderlyingType(t);
    if (value != null)
    {
        try
        {
            convertedValue = (T)System.Convert.ChangeType(value, t, CultureInfo.CurrentCulture);
            return true;
        }
        catch
        {
        }
    }
    return false;
}
RogerN
  • 3,761
  • 11
  • 18