This is Part 4 of a series on generic parsing. Here are parts 1, 2, and 3.
It's most efficient to call Parse and TryParse statically, but that requires knowledge of the type at compile time. The power of Parse<T> and TryParse<T> lies in the ability to call them with a type parameter. That is, in our own generic methods (including those that inherit their type parameters from the declaring class), we can receive any type parameter and, as long as that type implements Parse or TryParse, parse a string as that type.
However, Parse<T> and TryParse<T> require that parsers be declared by the type being parsed and not in a base class so that the fully derived type can be returned. As a result, they cannot directly parse nullable value types or enums. For example, the following won't compile:
int? value = null;
int.TryParse("1", out value); // Type mismatch on output parameter
int?.TryParse("1", out value); // No such method implemented on int?
Unfortunately, Parse<T> and TryParse<T> not only inherit this drawback, but defer revealing this fact until run time. The following will compile, but it'll throw an exception:
int? value;
"1".TryParse(out value); // Throws an exception
Instead, a temporary variable is necessary, like so:
int? value = null;
int tempValue;
if ("1".TryParse(out tempValue))
value = tempValue;
You could implement your own wrapper around this pattern for each type and call SetTryParseMethod. However, this could become tedious and yields a lot of seemingly unncessary code. Even if you implemented a single generic method to do this, you would still have to call SetTryParseMethod for each nullable value type you need to support.
Since I believe in not deviating from the expected behavior of a well-established pattern, especially when I'm trying to promote that pattern, I chose to let Parse<T> and TryParse<T> continue to inherit the aforementioned drawback. Instead, I wrote another set of methods, SmartParse<T>, SmartTryParse<T>, SmartParseDefault<T>, and their non-generic counterparts.
Handling Nullable Value Types
Since Nullable<T> is a wrapper around T, nullable value types can't implement parsers.
If the type is a nullable value type, SmartTryParse gets the underlying type and passes it to TryParse. Otherwise, it passes the original type to TryParse.
Type underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
type = underlyingType;
return TryParse(s, type, out result);
Now, we can do what we originally set out to do:
int? value = null;
"1".SmartTryParse(out value);
This works without any special handling on our part by virtue of how the .NET Framework boxes and unboxes nullable value types and the fact that the value type is unwrapped in the non-generic versions of these functions, where the value is passed and returned as object.
Handling Enums
The parsing functionality for enums is provided by their common base class, System.Enum, because enums can't declare any members other than public constant fields. Even ILASM prevents methods from being added to enums. Therefore, enums can't implement parsers.
Again, defining parsers for each enum type would be tedious. That's why SmartParse<T> and SmartTryParse<T> handle enums as a special case. After unwrapping the type from Nullable<T> (if necessary) and discovering that the type is an enum, they follow the appropriate pattern for the method being called. Parsing an output format from the query string and falling back on a default if an invalid value is specified is a one-liner:
OutputFormat format = Request.QueryString["format"].SmartParseDefault(OutputFormat.Xml);
Wrapping It Up
For the strictest adherance to the Parse/TryParse implementations, use Parse<T>, TryParse<T>, and ParseDefault<T>. But for a more liberal interpretation of nullable types and enums (one that will actually work!), use SmartParse<T>, SmartTryParse<T>, and SmartParseDefault<T>. The whole suite can be found in the attached project.
That does it for this series on generic parsing. I hope it's been helpful.
Attachment: GenericParsing.zip
0 comments :: Generic Parsing, Part 4: Smarter Parsing
Post a Comment