views:

288

answers:

5

Say I have a method that takes an int as a string and returns the int if the parse succeeds or a null value otherwise.

    int? ParseValue(string intAsString)
    {
        int i;
        if (int.TryParse(intAsString, out i))
            return i;
        return null;
    }

How can this method be re-written so that it works not only with int?, but also long?, decimal? and DateTime? ?

+8  A: 

It's funny you should mention it because I was messing around with something just like this the other day:

using System;
using System.Reflection;

static class Example
{
    public static Tuple<Boolean, T?> TryParse<T>(this String candidate)
        where T : struct
    {
        T? value = null;
        Boolean success = false;

        var parser = ParsingBinder<T>.GetParser();

        try 
        { 
                value = parser(candidate);
                success = true;
        } 
        catch (FormatException) { }

        return new Tuple<Boolean,T?>(success, value);
    }
}

static class ParsingBinder<T>
{
    static Func<String, T> parser;

    public static Func<String, T> GetParser()
    {
        if (parser == null)
                parser = getParser();

        return parser;
    }

    static Func<String, T> getParser()
    {
        MethodInfo methodInfo 
            = typeof(T).GetMethod(
              "Parse", new [] { typeof(String) });

        if (methodInfo == null)
                throw new Exception(
                     "Unable to retrieve a \"Parse\" method for type.");

        return (Func<String, T>)Delegate
     .CreateDelegate(typeof(Func<String, T>), methodInfo);
    }
}

It is a similar approach but think of it like a better TryParse method that returns a Tuple<Boolean, T?> (this requires .NET 4). The first property of the tuple is a boolean value indicating the success or failure of the parsing attempt and the second property is a nullable value typed to the generic type argument that will be null if parsing fails and the value if parsing succeeds.

It works by using reflection to retrieve a static Parse(String) method from the generic type argument and invokes that method for the string that is passed in. I built it as an extension method to allow you to do stuff like this:

var intValue = "1234".TryParse<Int32>();
var doubleValue = "1234".TryParse<Double>();

Unfortunately this won't work on enums since they don't have the same signature for the parse method so you couldn't use this extension to parse an enum but it wouldn't be hard to hack this up to make a special case for enums.

One of the nice things about this approach is that the cost of retrieving the Parse method via reflection is only incurred on the first use since a static delegate is created for all subsequent uses.


One more thing - the only thing that is clunky about this approach is that there is no language extensions or syntactic sugar that would make this easy to work with. What I was hoping to achieve with this code was a less clunky way of using the standard TryParse methods that exist in the BCL.

I personally find this pattern rather ugly:

Int32 value;
if (Int32.TryParse(someString, out value))
    // do something with value

mainly because it requires a variable declaration ahead of time and the use of an out parameter. My approach above isn't really that much better:

var result = someString.TryParse<Int32>();
if (result.Item1)
    // do something with result.Item2

What would be really cool would be to see a C# language extension that was built to work with a Tuple<Boolean, T?> that would allow us to work with this type smoothly but I get the feeling the more I write about this that it doesn't really seem that feasible.

Andrew Hare
Assuming this really works, +1 :P
o.k.w
Considering the primary purpose for the existence of `TryParse` is not throwing an exception on failure, your "better approach" has undone the purpose of its existence: http://www.codinghorror.com/blog/archives/000358.html
280Z28
@280z28 - Fair enough but the exception that I do throw is different. This exception is thrown when you try to parse a type that does not have a `TryParse(String)` method which never occurs when you call a normal `TryParse` method. This exception would undoubtedly be found by the developer in testing and wouldn't occur at runtime so it's not exactly the same thing as the exception the would be masked by a parsing failure.
Andrew Hare
@Andrew: The `FormatException` which you catch is the one I'm talking about. Catching the exception doesn't change the cost of throwing it. You should find and call a TryParse method and not the Parse method.
280Z28
Yes I could try and find a `TryParse` method to call but since the signatures for these methods vary greatly from type to type this would render the whole thing moot. Again this isn't much more than a half-baked idea that I remembered when reading the question.
Andrew Hare
The signature for TryParse that corresponds to the `static T Parse(string)` method is "always" `static bool T.TryParse(string, out T)`.
280Z28
Good point, for some reason I was thinking that the signatures varied (specifically for types like `DateTime`) but I think I am actually incorrect! Thanks!
Andrew Hare
I tried it out and it works as you described. Had to dispense with the Tuple since I'm not using C# 4, however a null return value is enough to signify a parse failure.
Phillip Ngan
@Philip - Glad to hear it! It is important to point out that 280Z28 is correct in saying that better solution would actually use a `TryParse` method under the covers, just keep that in mind.
Andrew Hare
If you're going to be returning a tuple, you should return `Tuple<boolean, T>` instead of `Tuple<boolean, T?>`. If parsing fails, set the second member to `default(T)`.
280Z28
+3  A: 

Instead of using the question mark, you can explicitly use the Nullable keyword: for example,

int? equals Nullable<int>

Therefore switching your original design to Nullable<T> ParseValue(string valueAsString) should do the trick: just do the generic implementation after this.

Dr. Xray
+1  A: 

If you can wait for C# 4.0, you can use the dynamic keyword, which solves such scenario.

Ron Klein
A: 

These types that you have listed all have a static method called TryParse. They look similar, but in fact the signatures are completely different. They follow a similar 'pattern', but that is not detectable by the compiler.

You might try to do something like this:

    public T? ParseValue<T>(string value) where T : struct
    {
        if (typeof(T) == typeof(int))
        {
            int i;
            if (int.TryParse(value, out i))
                return (T)(object)i;
            return null;
        }
        if (typeof(T) == typeof(decimal)) 
        {
            decimal d;
            if (decimal.TryParse(value, out d))
                return (T)(object)d;
            return null;
        }
        // other supported types...
        throw new ArgumentException("Type not supported");
    }

However, you can't. The compiler has no way to know (at compile time) how to convert type 'T' to an int (or any other type).

You can double-cast to make this work. (Thanks, Dotson)

Usage:

        var mydecimal = ParseValue<decimal>("12.1");
        var myint = ParseValue<int>("-22");
        var badint = ParseValue<int>("Bad");
        // badint.HasValue == false
Matt Brunell
Cast i to an object first:(T)(object)i
Matt Dotson
A: 

Actually, You can update what Matt's code has done and make it, and here is the code:

enter code here:static T? TryParse<T>(string parse)
        where T : struct
    {
        Type t=typeof(T);
        if (t==typeof(int))
        {
            int i;
            if (int.TryParse(parse, out i))
                return (T)(object)i;
            return null;
            //Console.WriteLine(t.Name);
        }
        if (t == typeof(double))
        {
            double i;
            if (double.TryParse(parse, out i))
                return (T)(object)i;
            return null;
        }
        //blabla, more logic like datetime and other data types
        return null;
    }

And also, you can use it like this: double? i = TryParse("111.111"); int? a = TryParse("111");

jujusharp