Click here to Skip to main content
12,298,927 members (52,415 online)
Click here to Skip to main content
Add your own
alternative version

Stats

14.8K views
559 downloads
61 bookmarked
Posted

.NET Type Conversion Utility

, 14 Jan 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
Convert what System.Convert can't.

Introduction

The System.Convert class is great for converting from one type to another but as some people may have run across, it can only convert types that implement IConvertible, even if the class you are trying to convert to has explicit or implicit conversion operators. 

With generics and dynamic types available in .NET today, programmers find themselves working with unknown types more often. Sometimes we need to be able to check if that type can be converted to another type or if that type supports a defined cast operator for another type. 

Additionally, this utility presents an expansion on the default keyword, as it stands default can create one of two values, 0 (if the type is primitive value type), or null (if the type is a reference type).  The utility here will create a default instance of a type if it has a default constructor, or returns null if it does not have one. 

Background 

System.Convert can convert any type that implements IConvertible, but does not support types that do not implement that interface even if the type has implicit or explicit operators to convert to other IConvertible types. This is because the System.Convert utility first checks for the implementation of IConvertible before attempting to convert and throws an exception if the types do not implement that interface. 

So what happens when you are working with unknown types at compile time and need to verify that those types can convert to another type? Either you can say that the type you are working with needs to support IConvertible (which only supports primitive types), or you can just attempt a cast and check the exception.  

The ConvertAny utility gets around this by providing the following methods:

  • CanConvert<ToType>(object)
    • Tests if the object is convertible to ToType. Returns true if object can be converted or false if it cannot be.
  • CanConvert(Type, Type)
    • Tests if the from type can be converted to the to type. This works on type references only and does not require an object reference.
  • TryConvert<ToType>(object, out ToType)
    • Attempts to convert the type, much like the TryParse methods that you find on many primitive types. If the conversion fails ToType will be the default(ToType) (the system one, not the utility one). 
  •  Convert<ToType>(object)
    • This converts the type and will throw an exception if the object cannot be converted to the ToType. 
  • ConvertByType(object, Type)
    • This converts the object using a Type reference. If the object cannot be converted to the type it will throw an exception.
  • Default<T>()
    • Creates a default instance of the type. If the type is a primitive type it has the same behavior as the default keyword. If the type has a default constructor, it will create an instance of the type. If the type does not have a default constructor this will return null.
  • DefaultByType(Type)
    • Same as above only it uses a Type reference instead of a generic. 

How type checking/conversion works

Conversion can be an expensive operation so it is important to try the least expensive operations to the more expensive operations in that order. This will keep the speed up in the case that objects are easily convertible to another type. 

Lets take a look at the CanConvert<ToType>(object) code: 

        public static bool CanConvert<ToType>(object value)
        {
            if (value == null)
                throw new ArgumentNullException("value", StringResources.ArgumentNullException);

            Type tType = typeof(ToType);
            Type fType = value.GetType();

            if (fType == tType)
                return true;

            if (typeof(IConvertible).IsAssignableFrom(tType) &&
                typeof(IConvertible).IsAssignableFrom(fType))
            {
                return true;
            }

            try
            {
                //Casting a boxed object to a type even though the type supports an explicit cast to
                //that type will fail. The only way to do this is to try to find the explicit or
                //implicit type conversion operator on the to type that supports the from type.
                MethodInfo mi = tType.GetMethods().FirstOrDefault(m =>
                    (m.Name == "op_Explicit" || m.Name == "op_Implicit") &&
                    m.ReturnType == tType &&
                    m.GetParameters().Length == 1 &&
                    m.GetParameters()[0].ParameterType == fType
                    );

                if (mi == null)
                {
                    //We can search for the reverse one on the original type too...
                    mi = fType.GetMethods().FirstOrDefault(m =>
                        (m.Name == "op_Explicit" || m.Name == "op_Implicit") &&
                        m.ReturnType == tType &&
                        m.GetParameters().Length == 1 &&
                        m.GetParameters()[0].ParameterType == fType
                        );
                }

                if (mi != null)
                    return true;

                return false;
            }
            catch
            {
                return false;
            }
        } 

This code pattern is the same throughout the other functions, only modified to either return a value, a boolean, or use an out parameter for the TryConvert methods.

As you can see, the first thing that we check is if the types are the same, if they are just return true. Next we will check to see if the types are both IConvertible, and if they are we know that System.Convert can easily convert them so we return true. 

The last part is where the meat is, if you read the comments inside the try you'll see that casting boxed object can fail even if those objects define operators to convert from one to the other. This was a good couple hours of frustration for me because the cast would work fine in the Watch window, and I can see that the object has the conversion operators, but it would throw an exception every time.

In order to get around this, we have to actually invoke the operator through reflection instead of using the cast. It would have been easy to write (ToType)value inside the try and if that worked return true, or false inside the catch, however that doesn't work.

Since the conversion cast may be defined on either type, we need to check both types for the cast operators. If it isn't found on the first type, we will check the second type for the operator. If either one of those checks finds the operator, it works.  

The new default 

The implementation of the default keyword has irked me for some time. There are only three possible options for the default, either it returns 0 for primitive types like int, long, double, float, it returns the basic constructed type for structs (since structs are not nullable), or it returns null for reference types. 

It would have been nice that for reference types if the type contains a default constructor, that it returns a default instance of the type. I needed this behavior in some of my code, so I wrote an extension to the Type class that can create default instances:

public static object CreateDefault(this Type t)
{
    return ConvertAny.DefaultByType(t);
} 

 Which calls this function inside the ConvertAny class:

        public static T Default<T>()
        {
            if (typeof(T).IsPrimitive)
                return default(T);

            ConstructorInfo cInfo = typeof(T).GetConstructor(Type.EmptyTypes);

            if (cInfo == null)
                return default(T);

            return (T)cInfo.Invoke(null);
        }

        public static object DefaultByType(Type type)
        {
            MethodInfo generic = _genericDefaultMethod.MakeGenericMethod(type);

            return generic.Invoke(null, null);
        } 

The _genericDefaultMethod is simply a cached reference to the Default<T> function that is created when the ConvertAny static constructor is run. While pretty simple, it does some interesting things. First it checks to see if the type is a primitive, and if it is just returns the normal default(T) for that type. Next it looks for a constructor that has no arguments (default constructor) and if it finds one, will return a default instance of that type.

Using the code 

Included in the project is the unit tests that prove the operation of the class, it provides many examples of how to use the different functions since it includes a test for each public method defined in both the ConvertAny and the TypeExtensions classes.  Here we will just go over some basic uses:

Finding if a type can be converted to another type

In order to do this, you can either use the CanConvert<ToType>(object) function if you have a strong type reference, or you can use the CanConvert(Type, Type) function if you only have a reference to the Type objects for the values.  

ConvertAny.CanConvert<bool>(1);     //Check if int 1 can be converted to bool
ConvertAny.CanConvert<TestCastibleClass>((double)1.0);  //Check if double 1.0 can be converted to TestCastibleClass
ConvertAny.CanConvert(typeof(int), typeof(bool));  //Test if an int can be converted to a bool
ConvertAny.CanConvert(typeof(TestCastibleClass), typeof(double));  //Test if TestCastibleClass can be converted to double 

Attempt to convert one type to another 

If you want to combine converting and the check into a single call (and better performance) then you can use the TryConvert<ToType>(object, out ToType) method. Why doesn't the library have a TryConvertByType(object, Type, out object) method? I leave that as an exercise for you, in order to implement it look at the Convert and ConvertByType methods.

bool b = false;
ConvertAny.TryConvert<bool>(1, out b);

TestCastibleClass tc = null;
ConvertAny.TryConvert<TestCastibleClass>(15.2, out tc); 

 Convert a value 

If you want to skip checking and just convert the value, you can use the Convert<ToType>(object) or ConvertByType(object, Type) methods. Be aware though, these will throw an InvalidCastException if they fail to convert the object to the desired type.   

ConvertAny.Convert<bool>(0);
ConvertAny.Convert<TestCastibleClass>(12.2);
ConvertAny.ConvertByType(1, typeof(bool));
ConvertAny.ConvertByType(new TestCastibleClass(), typeof(double)); 

Working with the type extensions

There are two extensions to the type as outlined earlier, the first is to get the default instance of a type CreateDefault and the second is to check if the type is convertible to another type, CanConvertTo(Type).  

object o = typeof(TestCastibleClass).CreateDefault();
typeof(int).CanConvertTo(typeof(double));
typeof(TestCastibleClass).CanConvertTo(typeof(double)); 

Points of Interest

Why go through all the trouble when .NET introduced the dynamic runtime types in version 4? 

Originally I designed this class to convert any dynamic type to any other type. To be honest the class was much simpler to implement with the dynamic keyword than it was with the object one, type casting on boxed types worked, and conversion was simple. 

Then I designed my unit tests (yes, I know, tests should be done first), and was shocked when I ran them. Some of the conversion routines took 100 milliseconds to run. I had to walk away from my computer for a good 30 minutes, and when I sat down to research it I found the problem. Dynamic types in .NET are extremely fast if they can be cached. When the DLR encounters a dynamic type it adds the site to a lookup so that the next time it doesn't have to build the type information. It can't do this in a static class that gets called with dynamic parameters because the parameters change.  

This caused a huge performance hit, since my original application for this class was to be used in a scripting library that did not work on statically typed objects and may include hundreds of checks/conversions per run. When I re-wrote the ConvertAny utility to use objects and reflection, the performance increased roughly 100 times (now takes 1ms or less to run particular tests).

The lesson here is that while dynamic may make your life easier, you must be aware of performance implications when working with it. Test throughly (even after you wrote the code) and make sure that your performance is on par with what your application requires.   

History 

1/13/2014 - Initial Version

1/14/2014 - Added null reference checking and reduced the use of typeof in the functions, thanks to Oliver Albrecht.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ron Beyer
President 6D Systems LLC
United States United States
I studied Software Engineering at Milwaukee School of Engineering for 2 years before switching to Management of Information Systems for a more business oriented approach. I've been developing software since the age of 14, and have waded through languages such as QBasic, TrueBasic, C, C++, Java, VB6, VB.NET, C#, etc. I've been developing professionally since 2002 in .NET.

You may also be interested in...

Comments and Discussions

 
Questionop_Explicit/op_Implicit methods not found by Type.GetMethods Pin
alextza9-Oct-14 2:35
memberalextza9-Oct-14 2:35 
GeneralMy vote of 5 Pin
Mihai MOGA14-Feb-14 17:05
professionalMihai MOGA14-Feb-14 17:05 
SuggestionConsider this Pin
sobo12320-Jan-14 19:08
membersobo12320-Jan-14 19:08 
GeneralRe: Consider this Pin
Ron Beyer20-Jan-14 19:19
mvpRon Beyer20-Jan-14 19:19 
GeneralNice Pin
Thorsten Bruning15-Jan-14 8:02
memberThorsten Bruning15-Jan-14 8:02 
GeneralRe: Nice Pin
Ron Beyer15-Jan-14 8:09
mvpRon Beyer15-Jan-14 8:09 
GeneralRe: Nice Pin
Thorsten Bruning15-Jan-14 8:13
memberThorsten Bruning15-Jan-14 8:13 
GeneralWhy not to use extension methods? Pin
Igor Ladnik14-Jan-14 1:59
professionalIgor Ladnik14-Jan-14 1:59 
GeneralRe: Why not to use extension methods? Pin
Ron Beyer14-Jan-14 3:21
mvpRon Beyer14-Jan-14 3:21 
GeneralRe: Why not to use extension methods? Pin
Igor Ladnik14-Jan-14 3:30
professionalIgor Ladnik14-Jan-14 3:30 
GeneralRe: Why not to use extension methods? Pin
Ron Beyer14-Jan-14 3:39
mvpRon Beyer14-Jan-14 3:39 
GeneralRe: Why not to use extension methods? Pin
jfriedman23hrs 56mins ago
professionaljfriedman23hrs 56mins ago 
SuggestionNull-Check Pin
Oliver Albrecht13-Jan-14 21:04
memberOliver Albrecht13-Jan-14 21:04 
GeneralRe: Null-Check Pin
Ron Beyer14-Jan-14 3:23
mvpRon Beyer14-Jan-14 3:23 
GeneralMy vote of 4 Pin
M Rayhan13-Jan-14 17:06
memberM Rayhan13-Jan-14 17:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160525.2 | Last Updated 14 Jan 2014
Article Copyright 2014 by Ron Beyer
Everything else Copyright © CodeProject, 1999-2016
Layout: fixed | fluid