Introduction
Recently, I encountered a situation where I needed to use a generic converter routine between C# compatible types, but where some of those, but not all, implement either implicit or explicit conversion operators. For this situation, I haven't found a generic solution that could cover all those possible cases.
My situation is that I'm retrieving records from a SQL database, and I have a single parsing routine through which I have to pass all possible fields, regardless of what specific SQL type they have. This routine converts them in real-time to the different objects that implement the application's logic. So, for instance, a given DateTime
object got from the SqlDataReader
has to be parsed to my Date
or Time
classes that I use in the application, and that implements its respective conversion operators.
The Solution
The following code implements a generic conversion solution that takes into consideration the possible existence of conversion operators in either the source or target classes. If any of those are found, the code uses them, or otherwise, it tries a last resource mechanism I found while researching in the Internet.
So, firstly, I have declared the two conversion methods in a static class so you can use them with an easy syntax as in TypeEx.ConvertTo(...)
or TypeEx.ConvertTo<T>(...)
, the second method being the generic version of the first one. In both cases, you pass the object to convert into the target type and a specification of this target type.
public static class TypeEx {
public static object ConvertTo( object source, Type targetType ) ...
public static T ConvertTo<t>( object source ) ...
}
The first step is to pass some sanity checks, and getting the source type's Type
. But in this case, I don't throw an exception, because I wait till I validate that the source object is null
, or not, and in the first case, if the target type is of a nullable one:
public static object ConvertTo( object source, Type targetType ) {
if( targetType == null ) throw new ArgumentNullException
( "Target type cannot be null." );
Type sourceType = source == null ? null : source.GetType();
if( source == null ) {
if( !IsNullableType( targetType ) )
throw new InvalidOperationException
( "Cannot convert NULL to a non-nullable type '" +
targetType + "'." );
return source;
}
I also made the TypeEx.IsNullableType(...)
method a member of the static class, having implemented it in this way:
public static bool IsNullableType( Type type )
{
if( type == null )
throw new ArgumentNullException( "Type object cannot be null." );
if( type.IsValueType ) return false;
if( type.IsClass ) return true;
Type generic = type.IsGenericType ? type.GetGenericTypeDefinition() : null;
bool r = generic == null ? false : generic.Equals( typeof( Nullable<> ) );
return r;
}
Now, the second step is finding, through Reflection, if an implicit or explicit conversion operator exists where the returning type is the same as the requested target type. If such methods exist, then the code invokes them and returns the result of such invocation:
MethodInfo[] sourceinfos = sourceType.GetMethods
( BindingFlags.Public | BindingFlags.Static );
foreach( MethodInfo info in sourceinfos ) {
if( ( info.Name == "op_Explicit" || info.Name == "op_Implicit" )
&& info.ReturnType == targetType ) {
object r = info.Invoke( source, new[] { source } );
return r;
}
}
MethodInfo[] targetinfos = targetType.GetMethods
( BindingFlags.Public | BindingFlags.Static );
foreach( MethodInfo info in targetinfos ) {
if( ( info.Name == "op_Explicit" || info.Name == "op_Implicit" )
&& info.ReturnType == targetType ) {
object r = info.Invoke( source, new[] { source } );
return r;
}
}
Finally, no conversion operators were implemented neither in the source nor target classes. As a last resort case, I use the following code that tries to force a conversion to the underlying target type:
if( targetType.IsGenericType && targetType.GetGenericTypeDefinition().Equals
( typeof( Nullable<> ) ) ) {
NullableConverter nc = new NullableConverter( targetType );
targetType = nc.UnderlyingType;
}
return Convert.ChangeType( source, targetType );
Conclusion
In many circumstances, when you implement your own classes, you also implement their conversion operators. The code I present here takes into consideration when those operators exist to use them for a generic conversion routine you can use with confidence. It will convert among standard CLR types as well.