Introduction
This class contains a Dictionary
of methods that allow parsing string
values. Methods other than those provided by the datatype
may be used. Once configured, all that is required to perform a parse are the value and the FullName
of the type you want.
Background
I have a particular class that gets its configuration from an XML file. An entry might be:
<StartTime DataType="System.DateTime" >2008-12-06 12:34:56</StartTime>
Obviously I want to be able to parse the value into a DateTime
, but all I have to go by is the name of the type; I need to parse the name as well!
Getting a type by its name isn't difficult:
System.Type t = System.Type.GetType ( "System.DateTime" ) ;
But then getting the Parse
method requires Reflection
:
System.Reflection.MethodInfo m = t.GetMethod
(
"Parse"
,
System.Reflection.BindingFlags.Public
|
System.Reflection.BindingFlags.Static
|
System.Reflection.BindingFlags.Instance
,
null
,
new System.Type[] { typeof(System.String) }
,
null
) ;
Reflection
is great, but it's costly, so caching the retrieved information is generally worthwhile.
Another problem we run into is that not every type I want to support has a public Parse
method that takes a string
as its only parameter:
System.Guid
has a constructor that can be used instead (and what's up with that anyway?) - Enumerations use the
System.Enum.Parse
method (which requires the type as well as the value) System.String
has a Copy
method we can use instead (String
is included so as not to have exceptional cases)
Concept
So I want a Dictionary
of datatype
names and their associated MethodBase
instances (MethodInfo
and ConstructorInfo
both derive from MethodBase
). I decided I also want a Dictionary
that contains the default values for the types so I can return a reasonable value when a TryParse
fails.
Other points:
- I want to pre-populate the dictionaries with some common
datatype
s. - I want to allow methods that come from "helper"
datatype
s. - I want to allow the user to add other
datatype
s with their methods and defaults. - I want to allow the user to override existing methods and defaults.
- I want to implement
Parse
and TryParse
for datatype
s that don't have their own.
Parsomatic
I chose to make this a static
class because I don't expect to need more than one:
public static class Parsomatic
{
}
Fields
The class contains only the two dictionaries and an array that we can use when we call GetMethod
:
private static readonly System.Type[] types ;
private static readonly System.Collections.Generic.Dictionary
<string,System.Reflection.MethodBase> parsers ;
private static readonly System.Collections.Generic.Dictionary<string,object> defaults ;
Constructor
The constructor instantiates and pre-populates the fields:
types = new System.Type[] { typeof(System.String) } ;
parsers = new System.Collections.Generic.Dictionary<string,System.Reflection.MethodBase>
( System.StringComparer.CurrentCultureIgnoreCase ) ;
defaults = new System.Collections.Generic.Dictionary<string,object>
( System.StringComparer.CurrentCultureIgnoreCase ) ;
(I'll show the pre-population later.)
AddType ( object )
This overload of AddType
is the simplest, it will determine the type to use automatically:
public static void
AddType
(
object Default
)
{
if ( Default == null )
{
throw ( new System.ArgumentNullException
( "Default" , "The Default must not be null" ) ) ;
}
if ( Default.GetType().IsEnum )
{
AddType
(
Default
,
typeof(EnumParser<>).MakeGenericType
( new System.Type[] { Default.GetType() } )
) ;
}
else
{
AddType
(
Default
,
Default.GetType()
) ;
}
return ;
}
(I'll describe EnumParser<>
later.)
This overload is used for pre-populating the following types:
AddType ( default(System.Byte) ) ;
AddType ( default(System.SByte) ) ;
AddType ( default(System.Int16) ) ;
AddType ( default(System.UInt16) ) ;
AddType ( default(System.Int32) ) ;
AddType ( default(System.UInt32) ) ;
AddType ( default(System.Int64) ) ;
AddType ( default(System.UInt64) ) ;
AddType ( default(System.Single) ) ;
AddType ( default(System.Double) ) ;
AddType ( default(System.Decimal) ) ;
AddType ( default(System.DateTime) ) ;
AddType ( default(System.Boolean) ) ;
AddType ( object , Type )
This overload calls GetMethod
on the provided type to find a public Parse
method that takes a string
as its only parameter:
public static void
AddType
(
object Default
,
System.Type Type
)
{
if ( Default == null )
{
throw ( new System.ArgumentNullException
( "Default" , "The Default must not be null" ) ) ;
}
if ( Type == null )
{
throw ( new System.ArgumentNullException
( "Type" , "The Type must not be null" ) ) ;
}
AddType
(
Default
,
Type.GetMethod
(
"Parse"
,
System.Reflection.BindingFlags.Public
|
System.Reflection.BindingFlags.Static
|
System.Reflection.BindingFlags.Instance
,
null
,
types
,
null
)
) ;
return ;
}
AddType ( object , MethodBase )
This overload ensures that the provided method meets certain criteria and, if it does, updates the dictionaries:
public static void
AddType
(
object Default
,
System.Reflection.MethodBase Method
)
{
if ( Default == null )
{
throw ( new System.ArgumentNullException
( "Default" , "The Default must not be null" ) ) ;
}
if ( Method == null )
{
throw ( new System.ArgumentNullException
( "Method" , "The Method must not be null" ) ) ;
}
if
(
( Method.GetParameters().Length != 1 )
||
( Method.GetParameters() [ 0 ].ParameterType != typeof(System.String) )
)
{
throw ( new System.ArgumentException
( "The Method must take one string parameter" , "Method" ) ) ;
}
if
(
( Method is System.Reflection.MethodInfo )
&&
( ((System.Reflection.MethodInfo) Method).ReturnType != Default.GetType() )
)
{
throw ( new System.ArgumentException
( "The Method must return the same type as the Default" , "Method" ) ) ;
}
if
(
( Method is System.Reflection.ConstructorInfo )
&&
( ((System.Reflection.ConstructorInfo) Method).ReflectedType
!= Default.GetType() )
)
{
throw ( new System.ArgumentException
( "The Method must return the same type as the Default" , "Method" ) ) ;
}
defaults [ Default.GetType().FullName ] = Default ;
parsers [ Default.GetType().FullName ] = Method ;
return ;
}
This overload is used for pre-populating the following types:
AddType
(
default(System.Guid)
,
typeof(System.Guid).GetConstructor
(
System.Reflection.BindingFlags.Public
|
System.Reflection.BindingFlags.Instance
,
null
,
types
,
null
)
) ;
AddType
(
""
,
typeof(System.String).GetMethod
(
"Copy"
,
System.Reflection.BindingFlags.Public
|
System.Reflection.BindingFlags.Static
,
null
,
types
,
null
)
) ;
Note that the default value for string
s is an empty string
rather than a null
.
Contains ( string )
public static bool
Contains
(
string Type
)
{
if ( Type == null )
{
throw ( new System.ArgumentNullException
( "Type" , "The Type must not be null" ) ) ;
}
return ( parsers.ContainsKey ( Type ) ) ;
}
Contains ( Type )
public static bool
Contains
(
System.Type Type
)
{
if ( Type == null )
{
throw ( new System.ArgumentNullException
( "Type" , "The Type must not be null" ) ) ;
}
return ( parsers.ContainsKey ( Type.FullName ) ) ;
}
Types
public static System.Type[]
Types
{
get
{
return ( (System.Type[]) types.Clone() ) ;
}
}
Parse ( string , string )
This overload of Parse
is called by the others. Note that it works whether the provided parser is a constructor, static
method, or instance method (though I don't know why an instance method would be used).
public static object
Parse
(
string Type
,
string Value
)
{
if ( Type == null )
{
throw ( new System.ArgumentNullException
( "Type" , "The Type must not be null" ) ) ;
}
if ( Value == null )
{
throw ( new System.ArgumentNullException
( "Value" , "The Value must not be null" ) ) ;
}
if ( !parsers.ContainsKey ( Type ) )
{
throw ( new System.ArgumentException ( "Unsupported type" , "Type" ) ) ;
}
try
{
object temp = defaults [ Type ] ;
return ( parsers [ Type ].Invoke ( temp , new object[] { Value } ) ?? temp ) ;
}
catch ( System.Exception err )
{
throw ( new System.ArgumentException ( "Could not parse" , "Value" , err ) ) ;
}
}
Parse ( type , string )
public static object
Parse
(
System.Type Type
,
string Value
)
{
if ( Type == null )
{
throw ( new System.ArgumentNullException
( "Type" , "The Type must not be null" ) ) ;
}
return ( Parse ( Type.FullName , Value ) ) ;
}
TryParse ( string , string , out object )
public static bool
TryParse
(
string Type
,
string Value
,
out object Result
)
{
bool result = false ;
Result = null ;
if
(
( Type != null )
&&
( Value != null )
&&
( parsers.ContainsKey ( Type ) )
)
{
try
{
Result = Parse ( Type , Value ) ;
result = true ;
}
catch
{
Result = defaults [ Type ] ;
}
}
return ( result ) ;
}
TryParse ( Type , string , out object )
public static bool
TryParse
(
System.Type Type
,
string Value
,
out object Result
)
{
bool result = false ;
Result = null ;
if
(
( Type != null )
&&
( Value != null )
&&
( parsers.ContainsKey ( Type.FullName ) )
)
{
try
{
Result = Parse ( Type.FullName , Value ) ;
result = true ;
}
catch
{
Result = defaults [ Type.FullName ] ;
}
}
return ( result ) ;
}
EnumParser<>
EnumParser
is an example of how a class may be written to provide a custom parser for another type. In this case, EnumParser.Parse
wraps System.Enum.Parse
, it is used by AddType ( object )
.
NumberParser<>
NumberParser
is included as another example of how a class may be written to provide a custom parser for another type. In this case, NumberParser.Parse
will parse the standard numeric types and provide hexadecimal support. The class was written merely as a simple demonstration and is not intended for actual use.
Using the Code
The zip file also contains ParsomaticTest.cs which contains a few simple examples of how the Parsomatic
may be used:
PIEBALD.Types.Parsomatic.AddType ( System.DateTime.Today ) ;
PIEBALD.Types.Parsomatic.AddType ( System.StringSplitOptions.None ) ;
PIEBALD.Types.Parsomatic.Parse ( "System.StringSplitOptions" , "RemoveEmptyEntries" ) ;
PIEBALD.Types.Parsomatic.AddType
( 0 , typeof(PIEBALD.Types.Parsomatic.NumberParser<System.Int32>) ) ;
PIEBALD.Types.Parsomatic.Parse ( "System.Int32" , "0xFF" ) ;
PIEBALD.Types.Parsomatic.AddType
(
PIEBALD.Types.Rational.Zero
,
typeof(PIEBALD.Types.Rational).GetMethod
(
"ParseInfix"
,
System.Reflection.BindingFlags.Public
|
System.Reflection.BindingFlags.Static
,
null
,
PIEBALD.Types.Parsomatic.Types
,
null
)
) ;
PIEBALD.Types.Parsomatic.Parse ( "PIEBALD.Types.Rational" , "1/3" ) ;
History
- 2008-12-06 First submitted