Posts
208
Comments
1144
Trackbacks
51
TryParse Extension Methods

When .NET 2.0 was released, a TryParse() method was added to many value types such as Int32, DataTime, etc. This was a huge improvement because there was no longer a need to catch expensive exceptions and drag down performance. However, one drawback of the TryParse() method is that the syntax is a little clunky. The most common example looks like this:

   1:  Person person = new Person();
   2:  string value = "21";
   3:  int num;
   4:  int.TryParse(value, out num);
   5:  person.Age = num;

So it's great that we can do all this without having to catch exceptions but it definitely seems more complex than it has to be given that all we're trying to do is assign an integer to the Age property and we need this many lines of code.  We can use Extension methods to make the consuming code much more friendly - a single line of code:

   1:  person.Age = value.TryParseInt32();

To implement the static class for the extension methods, we *could* implement each TryParseXX() method like this:

   1:  public static int TryParseInt32(this string value)
   2:  {
   3:      int result;
   4:      int.TryParse(value, out result);
   5:      return result;
   6:  }

However, now we've got the ugly, repetitive code in numerous methods inside that class. On the good side, at least we've encapsulated the repetitive code inside a single class, but we can leverage a generic delegate and generic method to even avoid the repetition inside that class.

   1:  private delegate bool ParseDelegate<T>(string s, out T result);
   2:   
   3:  private static T TryParse<T>(this string value, ParseDelegate<T> parse) where T : struct
   4:  {
   5:      T result;
   6:      parse(value, out result);
   7:      return result;
   8:  }

This allows our TryParseInt32() implementation to now be a single line of code:

   1:  public static int TryParseInt32(this string value)
   2:  {
   3:      return TryParse<int>(value, int.TryParse);
   4:  }

The ParseDelegate<T> is generic that matches the signature of all the TryParse() methods. So we can imply pass in the int.TryParse delegate and it will be executed *inside* the re-useable TryParse<T> method that I have created.  This utility class can be used in all kinds of different string parsing situations.  Here is the complete code for the TryParseExtensions class:

   1:  public static class TryParseExtensions
   2:  {
   3:     
   4:      public static int TryParseInt32(this string value)
   5:      {
   6:          return TryParse<int>(value, int.TryParse);
   7:      }
   8:   
   9:      public static Int16 TryParseInt16(this string value)
  10:      {
  11:          return TryParse<Int16>(value, Int16.TryParse);
  12:      }
  13:   
  14:      public static Int64 TryParseInt64(this string value)
  15:      {
  16:          return TryParse<Int64>(value, Int64.TryParse);
  17:      }
  18:   
  19:      public static byte TryParseByte(this string value)
  20:      {
  21:          return TryParse<byte>(value, byte.TryParse);
  22:      }
  23:   
  24:      public static bool TryParseBoolean(this string value)
  25:      {
  26:          return TryParse<bool>(value, bool.TryParse);
  27:      }
  28:   
  29:      public static Single TryParseSingle(this string value)
  30:      {
  31:          return TryParse<Single>(value, Single.TryParse);
  32:      }
  33:   
  34:      public static Double TryParseDoube(this string value)
  35:      {
  36:          return TryParse<Double>(value, Double.TryParse);
  37:      }
  38:   
  39:      public static Decimal TryParseDecimal(this string value)
  40:      {
  41:          return TryParse<Decimal>(value, Decimal.TryParse);
  42:      }
  43:   
  44:      public static DateTime TryParseDateTime(this string value)
  45:      {
  46:          return TryParse<DateTime>(value, DateTime.TryParse);
  47:      }
  48:   
  49:      #region Private Members
  50:   
  51:      private delegate bool ParseDelegate<T>(string s, out T result);
  52:   
  53:      private static T TryParse<T>(this string value, ParseDelegate<T> parse) where T : struct
  54:      {
  55:          T result;
  56:          parse(value, out result);
  57:          return result;
  58:      }
  59:   
  60:      #endregion
  61:   
  62:  }
posted on Sunday, October 5, 2008 5:30 PM Print
Comments
Gravatar
# re: TryParse Extension Methods
JeroenH
10/5/2008 3:37 PM
I firmly disagree with this approach. Since you're not using the boolean return value from the TryParse method, I guess you're assuming that the parsing will always succeed. In other words, if the input string is NOT a valid (int, double, ...), an exception can be thrown. So why not simply use the Parse() variant then?

If the input can be invalid (e.g. because it's user input), and you want to test it for correctness, you do need the return value from TryParse(), in order to handle the incorrect input string appropriately...
Gravatar
# re: TryParse Extension Methods
Steve
10/5/2008 3:46 PM
Yep, that is a totally valid point and I should have pointed out that disclaimer. The big caveat is that IF it is not valid then it will gracefully be set to a default value. This *may* or *may not* be desirable based on what you're trying to do. It could even be extended to allow a default value to be passed in similar to how the GetValueOrDefault() method works for nullable types.

Here is a scenario where this could have value. Let's say the use is posting from a ViewPage in MVC from a textbox that needs a number but the user typed letters. Using this would set the value to 0 since the parse would not work. The validation on that property would have to say that 0 was not valid. The original "attempted value" could still be passed in just fine.

Again, this is just an option and not a one-size fits all solution.
Gravatar
# re: TryParse Extension Methods
Divya Jain
10/6/2008 12:03 AM
Very good website. I liked it very much. Comments from http://www.valentineday.in
Gravatar
# re: TryParse Extension Methods
BEM
10/7/2008 4:24 PM
Good post. For comparison, below are a couple of generic TryParse extension methods. They'll each TryParse about anything that's TryParsable.

This one uses reflection and is about 50X slower than your methods.
Usage: var x = "3.1415".TryParse<float>();

public static T TryParse<T>(this string value)
{
Type t = typeof(T);
T type = default(T);

// Look for the TryParse method on the type.
System.Reflection.MethodInfo tryParse = t.GetMethod("TryParse", new Type[] { typeof(string), Type.GetType(t.FullName + "&") });
if (tryParse == null)
throw new NotSupportedException("TryParse is not supported on type " + t.FullName);
else
{
// Try parse exists. Call it.
Object[] ps = new Object[2];
ps[0] = value;
tryParse.Invoke(type, ps);
return (T)ps[1];
}
}


This one is about 10x slower and returns null when parsing fails.
Usage: var x = "3.1415".TryParse<float>();

public static T? TryParse2<T>(this string value) where T : struct
{
if (value == null) return null;

T? result = null;
var converter = TypeDescriptor.GetConverter(typeof(T));
if (converter != null)
{
try
{
result = (T)converter.ConvertFromString(value);
}
catch { }
}
return result;
}
Gravatar
# re: TryParse Extension Methods
Jay Kay
10/16/2008 3:22 PM
I'm going to have to agree with Brad and the other dude. What is it you hoped to accomplish here? You have failed miserably.
Gravatar
# re: TryParse Extension Methods
Asim Sajjad
11/5/2008 9:24 PM
Yes i am also agree with the other guys , what happen if the input is not value?
Gravatar
# re: TryParse Extension Methods
Dana Hanna
11/10/2008 12:06 PM
I much prefer my approach:

http://thesoftwarejedi.blogspot.com/2008/05/extension-methods.html

:)
Gravatar
# re: TryParse Extension Methods
Rob
7/15/2009 6:12 AM
I like the idea of your solution, however agree that the return result must be tested. Avoiding any try {} catch {} is a benefit in performance. Dana Hanna's solution works fine as well, but is significantly slower if you need heavy processing.

bool oK;
string s;
str = "123";
oK = str.IsTypeCode(TypeCode.Byte);
-> oK = true
str = "123.23";
oK = str.IsTypeCode(TypeCode.Byte);
-> oK = false

Here is the code, please note that I included workarounds for not supported TryParse data types:

public static class TryParseEx
{
public static bool IsTypeCode(this IConvertible obj, TypeCode typeCode)
{
string str = obj.ToString();
switch (typeCode)
{
case TypeCode.Boolean:
return TryParseOk<Boolean>(str, Boolean.TryParse);
case TypeCode.Byte:
return TryParseOk<Byte>(str, Byte.TryParse);
case TypeCode.Char:
return TryParseOk<Char>(str, Char.TryParse);
case TypeCode.DateTime:
return TryParseOk<DateTime>(str, DateTime.TryParse);
case TypeCode.DBNull:
// TryParse not supported
return (str == DBNull.Value.ToString());
case TypeCode.Decimal:
return TryParseOk<Decimal>(str, Decimal.TryParse);
case TypeCode.Double:
return TryParseOk<Double>(str, Double.TryParse);
case TypeCode.Empty:
// TryParse not supported
return String.IsNullOrEmpty(str);
case TypeCode.Int16:
return TryParseOk<Int16>(str, Int16.TryParse);
case TypeCode.Int32:
return TryParseOk<Int32>(str, Int32.TryParse);
case TypeCode.Int64:
return TryParseOk<Int64>(str, Int64.TryParse);
case TypeCode.Object:
// TryParse not supported
return (obj.GetTypeCode() == TypeCode.Object);
case TypeCode.SByte:
return TryParseOk<SByte>(str, SByte.TryParse);
case TypeCode.Single:
return TryParseOk<Single>(str, Single.TryParse);
case TypeCode.String:
// TryParseOk not supported, however true;
return true;
case TypeCode.UInt16:
return TryParseOk<UInt16>(str, UInt16.TryParse);
case TypeCode.UInt32:
return TryParseOk<UInt32>(str, UInt32.TryParse);
case TypeCode.UInt64:
return TryParseOk<UInt64>(str, UInt64.TryParse);
}
return false;
}
private delegate bool ParseDelegate<T>(string s, out T result);
private static bool TryParseOk<T>(this string str, ParseDelegate<T> parse) where T : struct
{
T result;
if (parse(str, out result))
return true;
else
return false;
}
}
Gravatar
# re: TryParse Extension Methods
Steve
7/15/2009 3:15 PM
@Rob - Thanks for taking the time to respond. Yeah, there's not a great solution to this. Dana's approach suffers from the same problem as my approach in that the result for parsing success is not testing. His also suffers from the perf of try/catch which you pointed out. I like your solution but it's only half the story. It's just check if the conversion will work but not doing the actual conversion. Bottom line seems to be that if you want to do the parsing in a safe and performant way while *also* property checking for conversion success, it's difficult to do that on only 1 line of code.
Gravatar
# re: TryParse Extension Methods
Rob
7/15/2009 6:10 PM
@Steve, agree nothing is perfect but we are getting closer if you add this:

double d;
string str = "123.12";
d = str.ConvertToDefault(Double.NaN);
-> d = 12312

str = "blabla"
d = str.ConvertToDefault(Double.NaN);
-> d = Double.NaN;

str = "blabla";
d = -1;
d = str.ConvertToDefault(d);
-> d = -1;


public static T ConvertToDefault<T>(this IConvertible obj, T other)
{
TypeCode typeCode = Convert.GetTypeCode(other);
if (obj.IsTypeCode(typeCode))
{
return (T)Convert.ChangeType(obj, typeCode);
}
return other;
}
Gravatar
# re: TryParse Extension Methods
Steve
7/16/2009 1:21 AM
@Rob - Yep, I think that's a fine approach too. In fact, it provides a very similar implementation of the API that was in the original post (though mine didn't give the opportunity to provide an "other" value, but if the parse fails then it returns the default of that type). However, that approach is what came under such heavy criticism by a couple of commenters above for the reason that the consuming code *needs* to check the success indicator in order to make an intelligent decision what to do.

Either way, I really think it depends on the specific scenario at hand. Whereas I see the point from the criticism above, I also do think there are some valid scenarios where API such as the one I proposed and the one you have above are perfectly valid.
Gravatar
# re: TryParse Extension Methods
Mukesh K M
5/28/2013 5:31 AM
public static T To<T>(this object input) where T : IConvertible
{
string type = typeof(T).Name;
TypeCode typecode;
Enum.TryParse(type, out typecode);
try
{
return (T)Convert.ChangeType(input, typecode);
}
catch
{
return default(T);
}
}

Post Comment

Title *
Name *
Email
Comment *  
Verification

View Steve Michelotti's profile on LinkedIn

profile for Steve Michelotti at Stack Overflow, Q&A for professional and enthusiast programmers




Google My Blog

Tag Cloud