Object to Object Mapper, Part 4

In the last post I talked about the Mapping class and showed a number of functions that allowed for type conversion, data formatting, etc. The end result is a very simple, yet powerful enough object to object mapper that it can be used in a number of situations. However, getting back to my original post, it doesn't fit my situation. Therefore what I needed was something that would allow an assembly to specify what properties within an object to map and, based on their type, the application would specify how it would like to receive that data so that the user can manipulate it. At the same time neither the assembly nor the application would know what they are getting from each other. In my first post I showed a very basic attempt to accomplish this, here I will show you the final implementation:

    /// <summary>
    /// Factory mapping
    /// </summary>
    /// <typeparam name="Source">Source type</typeparam>
    public class FactoryMapping<Source>:IMapping
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public FactoryMapping()
        {
            Setup();
        }

        #endregion

        #region IMapping Members

        public Type SourceType
        {
            get { return typeof(Source); }
        }

        public Type DestinationType
        {
            get { return typeof(List<object>); }
        }

        public void Sync(object Source, object Destination)
        {
            List<object> TempDestination = (List<object>)Destination;
            int x = 0;
            foreach (MappingInfo PropertyMapping in PropertyMappings)
            {
                if (!string.IsNullOrEmpty(PropertyMapping.Description))
                {
                    ++x;
                }
                PropertyInfo PropertyInfo = null;
                object TempSourceObject = Utilities.Reflection.GetPropertyParent(Source, PropertyMapping.Source, out PropertyInfo);
                SourceMapping.Sync(TempSourceObject, PropertyInfo, TempDestination[x]);
                ++x;
            }
        }

        public object Create(object Source)
        {
            List<object> ReturnList = new List<object>();
            foreach (MappingInfo PropertyMapping in PropertyMappings)
            {
                if (!string.IsNullOrEmpty(PropertyMapping.Description))
                {
                    ReturnList.Add(SourceMapping.CreateDescription(PropertyMapping.Description));
                }
                ReturnList.Add(SourceMapping.CreateDestination(Utilities.Reflection.GetPropertyValue(Source, PropertyMapping.Source),
                    Utilities.Reflection.GetPropertyType(Source,PropertyMapping.Source)));
            }
            return ReturnList;
        }

        #endregion

        #region Protected Functions

        /// <summary>
        /// Maps source property
        /// </summary>
        /// <param name="SourceExpression">Source property to map</param>
        protected void Map(Expression<Func<Source, object>> SourceExpression)
        {
            Map(SourceExpression, "");
        }

        /// <summary>
        /// Maps source property
        /// </summary>
        /// <param name="SourceExpression">Source property to map</param>
        /// <param name="Format">Format string</param>
        protected void Map(Expression<Func<Source, object>> SourceExpression, string Format)
        {
            Setup();
            string SourceName = Utilities.Reflection.GetPropertyName<Source>(SourceExpression);
            PropertyInfo SourceProperty = Utilities.Reflection.GetProperty<Source>(SourceName);
            object[] Attributes = SourceProperty.GetCustomAttributes(typeof(Description), true);
            if (Attributes.Length > 0)
            {
                PropertyMappings.Add(new MappingInfo(SourceName, "", Format, ((Description)Attributes[0]).DescriptionValue));
            }
            else
            {
                PropertyMappings.Add(new MappingInfo(SourceName, "", Format, ""));
            }
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Sets up the mappings
        /// </summary>
        private void Setup()
        {
            if (PropertyMappings != null)
                return;
            SourceMapping = (TypeMapping)Manager.Instance.TypeMapping;
            PropertyMappings = new List<MappingInfo>();
        }

        #endregion

        #region Internal Properties

        internal TypeMapping SourceMapping { get; set; }
        internal List<MappingInfo> PropertyMappings { get; set; }

        #endregion
    }

The code above is actually fairly similar to the Mapping class that I've already shown. We have our Map, Sync, and Create functions. However the mapping is one sided in this case. You'll notice that the only thing that is mapped is the source type and property. In both the Sync and Create functions you'll notice that it takes the mapped source info and feeds it to a TypeMapping class:

    /// <summary>
    /// Maps a type to another type
    /// </summary>
    public class TypeMapping : Factory<Type, object>, ITypeMapping
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public TypeMapping()
        {
            Setup();
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Sets up the mappings
        /// </summary>
        private void Setup()
        {
            if (PropertyMappings != null)
                return;
            PropertyMappings = new Dictionary<Type, MappingInfo>();
        }

        #endregion

        #region Protected Functions

        /// <summary>
        /// Maps a type to another type
        /// </summary>
        /// <typeparam name="Source">Source type</typeparam>
        /// <typeparam name="Destination">Destination type</typeparam>
        /// <param name="DestinationType">Function for creating the destination type</param>
        /// <param name="DestinationProperty">Property to map to</param>
        protected void Map<Source, Destination>(Func<object> DestinationType,
            Expression<Func<Destination, object>> DestinationProperty)
        {
            Setup();
            Register(typeof(Source), DestinationType);
            PropertyMappings.Add(typeof(Source), new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), "",""));
        }

        /// <summary>
        /// Maps a type to another type
        /// </summary>
        /// <typeparam name="Source">Source type</typeparam>
        /// <typeparam name="Destination">Destination type</typeparam>
        /// <param name="DestinationType">Function for creating the destination type</param>
        /// <param name="DestinationProperty">Property to map to</param>
        /// <param name="Format">Format string</param>
        protected void Map<Source, Destination>(Func<object> DestinationType,
            Expression<Func<Destination, object>> DestinationProperty,
            string Format)
        {
            Setup();
            Register(typeof(Source), DestinationType);
            PropertyMappings.Add(typeof(Source), new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), Format, ""));
        }

        /// <summary>
        /// Maps a description to another type
        /// </summary>
        /// <typeparam name="Destination">Destination type</typeparam>
        /// <param name="DestinationType">Function for creating the destination type</param>
        /// <param name="DestinationProperty">Property to map to</param>
        protected void MapDescription<Destination>(Func<object> DestinationType,
            Expression<Func<Destination, object>> DestinationProperty)
        {
            MapDescription<Destination>(DestinationType, DestinationProperty, "");
        }

        /// <summary>
        /// Maps a description to another type
        /// </summary>
        /// <typeparam name="Destination">Destination type</typeparam>
        /// <param name="DestinationType">Function for creating the destination type</param>
        /// <param name="DestinationProperty">Property to map to</param>
        /// <param name="Format">Formatting string</param>
        protected void MapDescription<Destination>(Func<object> DestinationType,
            Expression<Func<Destination, object>> DestinationProperty,
            string Format)
        {
            Register(typeof(MappingInfo), DestinationType);
            DescriptionMapping = new MappingInfo("", Utilities.Reflection.GetPropertyName<Destination>(DestinationProperty), Format, "");
        }

        #endregion

        #region Public Functions

        /// <summary>
        /// Creates the destination type
        /// </summary>
        /// <param name="SourceValue">Source value</param>
        /// <param name="SourceType">Source type</param>
        /// <returns>The destination type</returns>
        public object CreateDestination(object SourceValue, Type SourceType)
        {
            object DestinationObject = Create(SourceType);
            Utilities.Reflection.SetValue(SourceValue, DestinationObject, PropertyMappings[SourceType].Destination, PropertyMappings[SourceType].Format);
            return DestinationObject;
        }

        /// <summary>
        /// Creates a description object based off of the description input
        /// </summary>
        /// <param name="DescriptionValue">The description value</param>
        /// <returns>The description object</returns>
        public object CreateDescription(object DescriptionValue)
        {
            object DestinationObject = Create(typeof(MappingInfo));
            Utilities.Reflection.SetValue(DescriptionValue, DestinationObject, DescriptionMapping.Destination, DescriptionMapping.Format);
            return DestinationObject;
        }

        /// <summary>
        /// Syncs the source to the destination
        /// </summary>
        /// <param name="SourceObject">Source object</param>
        /// <param name="SourcePropertyInfo">Property to copy</param>
        /// <param name="DestinationObject">Destination object</param>
        public void Sync(object SourceObject, PropertyInfo SourcePropertyInfo, object DestinationObject)
        {
            object Value = Utilities.Reflection.GetPropertyValue(DestinationObject, PropertyMappings[SourcePropertyInfo.PropertyType].Destination);
            Utilities.Reflection.SetValue(Value, SourceObject, SourcePropertyInfo, PropertyMappings[SourcePropertyInfo.PropertyType].Format);
        }

        #endregion

        #region Internal Properties

        internal Dictionary<Type, MappingInfo> PropertyMappings { get; set; }
        internal MappingInfo DescriptionMapping { get; set; }

        #endregion
    }

The TypeMapping class acts in sort of reverse. It maps a data type to a destination type/property. So for instance you could map a string to a TextBox's Text property. The Map functions are slightly different as it wants a function that will create an object of the destination type (so you would feed it something like new Func<object>(() => new TextBox())). Anyway, the Sync and Create functions on the other hand simply take the data from the FactoryMapping and spits out the appropriate object. In essence it's very similar to the Mapping class but just splits the operations across two classes.

The benefit of doing it this way is that the manager doesn't need to change much. The only change is it needs to search for the ITypeMapping interface as well and save it in a property (TypeMapping). The down side though is the fact that the Syncing is one way. Having the generic TypeMapping class means that I never know what the type of the source is and therefore can't create one or sync to it. Thus every time you want to go from the source to the destination you must create the objects again. Otherwise it works perfectly well.

So we're good right? We have a way to get the data from the object in a format that the user can modify. Well, not really. We have the data but all the user is going to see is a textbox with no idea what to put into it. As such we need one last thing. You may have noticed that in the FactoryMapping class's Map function it's looking for a specific attribute (and specifically the DescriptionValue property of said attribute). It then feeds it to the TypeMapping using the CreateDescription function. This function in turn uses a specially designated MappingInfo class (DescriptionMapping), which is generated through the use of MapDescription. You see the reason for all of this was that I needed a way for the assembly to describe what the data was in such a way that the end user would know what they are modifying. As such, the business object would add this attribute:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class Description:Attribute
    {
        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="Value">Description of the property</param>
        public Description(string Value)
        {
            DescriptionValue = Value;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Description value
        /// </summary>
        public string DescriptionValue { get; set; }

        #endregion
    }

This is added to any property that they would like to map like this:

        [Description("Text Field Description: ")]
        public string Text { get; set; }

And in turn the application would specify that it wants any description to be a specific type (for instance a Label). Thus the app would get a list of objects that would contain the description as well as the object. So there we go, we have the assembly on one side supplying our objects and the app on the other showing the info to the user and neither one needs to know anything about the other.

On a side note, the code is now compiled and put into a nice little project on Codeplex: http://objectcartographer.codeplex.com/. So if you are interested in downloading the final code base, go there to check it out.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 2/16/2010 at 3:16 PM
Tags: ,
Categories: C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed

Object to Object Mapper, Part 3

In the last post in this series, I showed my second pass (or would you consider it a first pass since the original was more a factory pattern?) at an object to object mapper. It was rather basic and was missing a number of features which would make it really useable (type conversion, string formatting, etc.). Not to mention much of the code that I was working on needed to be moved into my utility library and cleaned up. Well now I'm back to give my third pass at it:

    /// <summary>
    /// Maps two objects together
    /// </summary>
    /// <typeparam name="Source">Source type</typeparam>
    /// <typeparam name="Destination">Destination type</typeparam>
    public class Mapping<Source,Destination>:IMapping
    {

        #region Constructor

        /// <summary>
        /// Constructor
        /// </summary>
        public Mapping()
        {
            Setup();
        }

        #endregion

        #region IMapping Members

        public Type SourceType { get { return typeof(Source); } }

        public Type DestinationType { get { return typeof(Destination); } }

        public void Sync(object SourceObject, object DestinationObject)
        {
            if (SourceObject.GetType() == SourceType)
            {
                for (int y = 0; y < Mappings.Count; ++y)
                {
                    object SourceValue = Utilities.Reflection.GetPropertyValue(SourceObject, Mappings[y].Source);
                    Utilities.Reflection.SetValue(SourceValue, DestinationObject, Mappings[y].Destination, Mappings[y].Format);
                }
                return;
            }
            for (int y = 0; y < Mappings.Count; ++y)
            {
                object DestinationValue = Utilities.Reflection.GetPropertyValue(SourceObject, Mappings[y].Destination);
                Utilities.Reflection.SetValue(DestinationValue, DestinationObject, Mappings[y].Source, Mappings[y].Format);
            }
        }

        public object Create(object Source)
        {
            if (Source.GetType() == SourceType)
            {
                object DestinationObject = typeof(Destination).Assembly.CreateInstance(typeof(Destination).FullName);
                Sync(Source, DestinationObject);
                return DestinationObject;
            }
            object SourceObject = typeof(Destination).Assembly.CreateInstance(typeof(Source).FullName);
            Sync(Source, SourceObject);
            return SourceObject;
        }

        #endregion

        #region Protected Functions

        /// <summary>
        /// Maps a source property to a destination property
        /// </summary>
        /// <param name="SourceExpression">Source property</param>
        /// <param name="DestinationExpression">Destination property</param>
        protected void Map(Expression<Func<Source, object>> SourceExpression,
            Expression<Func<Destination, object>> DestinationExpression)
        {
            Map(SourceExpression, DestinationExpression, "");
        }

        /// <summary>
        /// Maps a source property to a destination property
        /// </summary>
        /// <param name="SourceExpression">Source property</param>
        /// <param name="DestinationExpression">Destination property</param>
        /// <param name="Format">The string format that the destination should use</param>
        protected void Map(Expression<Func<Source, object>> SourceExpression,
            Expression<Func<Destination, object>> DestinationExpression,
            string Format)
        {
            Setup();
            string SourceName = Utilities.Reflection.GetPropertyName<Source>(SourceExpression);
            string DestinationName = Utilities.Reflection.GetPropertyName<Destination>(DestinationExpression);
            for (int x = 0; x < Mappings.Count; ++x)
            {
                if (Mappings[x].Destination == DestinationName)
                {
                    Mappings.RemoveAt(x);
                    break;
                }
            }
            for (int x = 0; x < Mappings.Count; ++x)
            {
                if (Mappings[x].Source == SourceName)
                {
                    Mappings.RemoveAt(x);
                    break;
                }
            }
            Mappings.Add(new MappingInfo(SourceName, DestinationName, Format));
        }

        /// <summary>
        /// Removes a mapping
        /// </summary>
        /// <param name="SourceExpression">Source property to ignore</param>
        protected void Ignore(Expression<Func<Source, object>> SourceExpression)
        {
            Setup();
            string SourceName = Utilities.Reflection.GetPropertyName<Source>(SourceExpression);
            for (int x = 0; x < Mappings.Count; ++x)
            {
                if (Mappings[x].Source == SourceName)
                {
                    Mappings.RemoveAt(x);
                    break;
                }
            }
        }

        /// <summary>
        /// Removes a mapping
        /// </summary>
        /// <param name="DestinationExpression">Destination property to ignore</param>
        protected void Ignore(Expression<Func<Destination, object>> DestinationExpression)
        {
            Setup();
            string DestinationName = Utilities.Reflection.GetPropertyName<Destination>(DestinationExpression);
            for (int x = 0; x < Mappings.Count; ++x)
            {
                if (Mappings[x].Destination == DestinationName)
                {
                    Mappings.RemoveAt(x);
                    break;
                }
            }
        }

        #endregion

        #region Private Functions

        /// <summary>
        /// Sets up the mapping
        /// </summary>
        public void Setup()
        {
            if (Mappings == null)
            {
                Mappings = new List<MappingInfo>();
                PropertyInfo[] Properties = typeof(Source).GetProperties();
                Type DestinationType = typeof(Destination);
                for (int x = 0; x < Properties.Length; ++x)
                {
                    if (DestinationType.GetProperty(Properties[x].Name) != null)
                    {
                        Mappings.Add(new MappingInfo(Properties[x].Name, Properties[x].Name, ""));
                    }
                }
            }
        }

        #endregion

        #region Protected Variables

        internal List<MappingInfo> Mappings { get; set; }

        #endregion
    }

The mapping class was described in the previous post and it works in a similar fashion. It's a base class that needs to be implemented, the manager class goes looking for it in the assembly that you specify, etc. In the constructor, you would still declare your mappings but this time you can now specify a formatting for strings. For instance I could have a DateTime map to a string and have it formatted however you wanted by calling Map(DateTimeProperty,StringProperty,"THE FORMAT I WANT"). Obviously not that exactly as you probably don't have those properties and that format string wouldn't work at all... Past that the mappings are no longer a simple Dictionary of source/destination paths. Instead it's a MappingInfo class, which is really just the source/destination paths and another string that holds the formatting. Otherwise the setup is about the same.

Well there is a slight change to the setup code. Namely they now call Utilities.Reflection.GetPropertyName. It's the same as the GetName function from the previous post, just moved into my utility library. Nowwhere the changes start rolling in is the Sync function and a new function called Create. In Sync, the first thing you may notice is the fact that it checks to see if the Source object is actually of type Source. The reason for this is really to make my life easier. In my first attempt, it only synced from the Source to the Destination. This was great if you were only sending data one way, but if you wanted to sync the other way you had to set up another mapping class. Sort of kills the whole less coding/making your life easier idea. So the manager now just checks if the source/destination match up (in either order) and sends in the objects. As such the Mapping class needs to figure out which direction it's going. Once the direction is established it calls Utilities.Reflection.GetPropertyValue, which can be found below:

        /// <summary>
        /// Gets a property's value
        /// </summary>
        /// <param name="SourceObject">object who contains the property</param>
        /// <param name="PropertyPath">Path of the property (ex: Prop1.Prop2.Prop3 would be
        /// the Prop1 of the source object, which then has a Prop2 on it, which in turn
        /// has a Prop3 on it.)</param>
        /// <returns>The value contained in the property or null if the property can not
        /// be reached</returns>
        public static object GetPropertyValue(object SourceObject, string PropertyPath)
        {
            if (SourceObject == null||string.IsNullOrEmpty(PropertyPath))
                return null;
            string[] Splitter = { "." };
            string[] SourceProperties = PropertyPath.Split(Splitter, StringSplitOptions.None);
            object TempSourceProperty = SourceObject;
            Type PropertyType = SourceObject.GetType();
            for (int x = 0; x < SourceProperties.Length; ++x)
            {
                PropertyInfo SourcePropertyInfo = PropertyType.GetProperty(SourceProperties[x]);
                if (SourcePropertyInfo == null)
                    return null;
                TempSourceProperty = SourcePropertyInfo.GetValue(TempSourceProperty, null);
                if (TempSourceProperty == null)
                    return null;
                PropertyType = SourcePropertyInfo.PropertyType;
            }
            return TempSourceProperty;
        }

In my case, I make no assumptions that you are only going to get shallow properties. For all I know you're going to want something like MyObject.CurrentDate.Month and map that to some Label somewhere. So the function takes a string path (in the example I gave it would actually be "CurrentDate.Month" assuming MyObject was the object and not a property) and simply loops through it trying to get to the property specified and its value. Anyway, the Sync function calls this to get the current source value and then feeds that to the Utilities.Reflection.SetValue function. This function is where it gets a bit more interesting.

        /// <summary>
        /// Sets the value of destination property
        /// </summary>
        /// <param name="SourceValue">The source value</param>
        /// <param name="DestinationObject">The destination object</param>
        /// <param name="PropertyPath">The path to the property (ex: MyProp.SubProp.FinalProp
        /// would look at the MyProp on the destination object, then find it's SubProp,
        /// and finally copy the SourceValue to the FinalProp property on the destination
        /// object)</param>
        /// <param name="Format">Allows for formatting if the destination is a string</param>
        public static void SetValue(object SourceValue, object DestinationObject,
            string PropertyPath, string Format)
        {
            string[] Splitter = { "." };
            string[] DestinationProperties = PropertyPath.Split(Splitter, StringSplitOptions.None);
            object TempDestinationProperty = DestinationObject;
            Type DestinationPropertyType = DestinationObject.GetType();
            PropertyInfo DestinationProperty = null;
            for (int x = 0; x < DestinationProperties.Length - 1; ++x)
            {
                DestinationProperty = DestinationPropertyType.GetProperty(DestinationProperties[x]);
                DestinationPropertyType=DestinationProperty.PropertyType;
                TempDestinationProperty = DestinationProperty.GetValue(TempDestinationProperty, null);
                if (TempDestinationProperty == null)
                    return;
            }
            DestinationProperty = DestinationPropertyType.GetProperty(DestinationProperties[DestinationProperties.Length - 1]);
            SetValue(SourceValue, TempDestinationProperty, DestinationProperty, Format);
        }

This function works in a similar fashion to the GetPropertyValue function to get the destination property that we want. This function then calls another version of SetValue:

        /// <summary>
        /// Sets the value of destination property
        /// </summary>
        /// <param name="SourceValue">The source value</param>
        /// <param name="DestinationObject">The destination object</param>
        /// <param name="DestinationPropertyInfo">The destination property info</param>
        /// <param name="Format">Allows for formatting if the destination is a string</param>
        public static void SetValue(object SourceValue, object DestinationObject,
            PropertyInfo DestinationPropertyInfo, string Format)
        {
            if (DestinationObject == null || DestinationPropertyInfo == null)
                return;
            Type DestinationPropertyType = DestinationPropertyInfo.PropertyType;
            DestinationPropertyInfo.SetValue(DestinationObject,
                Parse(SourceValue, DestinationPropertyType, Format),
                null);
        }

And this funcion sets the property's value but you'll notice that there is a call to Parse in the value portion of the PropertyInfo.SetValue function. This is once again, yet another function within the utility library:

        private static object Parse(object Input, Type OutputType,string Format)
        {
            try
            {
                if (Input==null || OutputType == null)
                    return null;
                Type InputType = Input.GetType();
                if (InputType == OutputType)
                {
                    return Input;
                }
                else if (OutputType == typeof(string) && !string.IsNullOrEmpty(Format))
                {
                    return StringHelper.FormatToString(Input, Format);
                }
                else if (OutputType == typeof(string))
                {
                    return Input.ToString();
                }
                else
                {
                    return CallMethod("Parse", OutputType.Assembly.CreateInstance(OutputType.FullName), Input.ToString());
                }
            }
            catch { throw; }
        }

This function determines if the objects are the same type. If they are, it simply returns the input object. If, however it is a string and the format string isn't null, it calls FormatToString (this is just the FormatToString function from my previous post). If it's a string but the format string is null, it simply calls Input.ToString(). And lastly if they are two different types (for example copying an int to a float), it calls yet another function called CallMethod:

        /// <summary>
        /// Calls a method on an object
        /// </summary>
        /// <param name="MethodName">Method name</param>
        /// <param name="Object">Object to call the method on</param>
        /// <param name="InputVariables">(Optional)input variables for the method</param>
        /// <returns>The returned value of the method</returns>
        public static object CallMethod(string MethodName, object Object,params object[] InputVariables)
        {
            if(string.IsNullOrEmpty(MethodName)||Object==null)
                return null;
            Type ObjectType = Object.GetType();
            MethodInfo Method = null;
            if (InputVariables != null)
            {
                Type[] MethodInputTypes = new Type[InputVariables.Length];
                for (int x = 0; x < InputVariables.Length; ++x)
                {
                    MethodInputTypes[x] = InputVariables[x].GetType();
                }
                Method = ObjectType.GetMethod(MethodName, MethodInputTypes);
                if (Method != null)
                {
                    return Method.Invoke(Object, InputVariables);
                }
            }
            Method = ObjectType.GetMethod(MethodName);
            if (Method != null)
            {
                return Method.Invoke(Object, null);
            }
            return null;
        }

You see, I've been writing a lot of code that would need to call a function of an object using reflection as of late. In this case I can simply tell it the name of the function, send in the object, and any needed input values. The function in turn looks for functions with the specified name that takes the input values (if there are any). If it finds one, it then invokes it, sending in the appropriate values. That's it, but it allows me to do away with rewriting that bit of code every time I need it. Anyway, in this case it's called looking for the Parse function on the object (int.Parse, float.Parse, etc.). It then returns the newly parsed item which then gets set as the property's value. Note that it doesn't do anything special, if you pass in 1.2 and want to convert it to an int, it will fail.

Anyway, that's it to the Sync function (once you parse through the 10 or so layers). The other function is the Create function. This goes back to my need to have this thing act like a factory pattern a bit. All the function does is creates a new instance of either the destination or source object (basically it creates the opposite of what is sent in) and then calls Sync. That's it. So there you go, we now have a simple object to object mapper that does type conversion and string formatting. I'm actually pretty happy with it at this point, although I could speed it up with some lightweight code gen like Gunner Peipman has been doing in the object to object mapper that he has been building. Which by the way you should take a look at if your needs are fairly simple as his implementation is pretty good at this point. Unfortunately it doesn't fit my needs or I'd use it. The next post I'll show the reworked version of my factory/mapper that I was working on in the first post to work with the newer code. So try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 2/12/2010 at 2:17 PM
Tags: , , ,
Categories: C#
Post Information: Permalink | Comments (2) | Post RSSRSS comment feed

Dynamically Parsing a String to Any Type When You Don't Know The Type

Last post I showed a very basic Object to Object Mapper. It works quite well for my needs, although I'm modifying it further to have a couple features that I need. The first is the simple fact that I need it to dynamically convert an object to a string (and allow for formatting) and back from that string to the object. To be honest, it's pretty simple. Every object inherits from object, a simple base class that has a couple of functions including ToString, however the object base class doesn't allow for formatting. For example, if the object is of type DateTime but we're only passed it as an object, all we can do is call the default ToString, right? Not exactly. We can actually call the ToString that allows formatting by simply using reflection:

        public static string FormatToString(object Input, string Format)
        {
            try
            {
                if (Input == null)
                    return "";
                if (!string.IsNullOrEmpty(Format))
                {
                    Type InputType = Input.GetType();
                    Type[] InputVariableTypes = new Type[1];
                    InputVariableTypes[0] = typeof(string);
                    MethodInfo Property = InputType.GetMethod("ToString", InputVariableTypes);
                    if (Property != null)
                    {
                        object[] InputVariables = new object[1];
                        InputVariables[0] = Format;
                        return (string)Property.Invoke(Input, InputVariables);
                    }
                    return Input.ToString();
                }
                return "";
            }
            catch { throw; }
        }

That's it. With that simple function you can take anything as an input with any format string and it will output the object formatted correctly. Well assuming it has a ToString function which allows for formatting. And to be honest, going back to the object is just as simple:

        public static object Parse(string Input, Type OutputType)
        {
            try
            {
                if (string.IsNullOrEmpty(Input) || OutputType == null)
                    return null;
                Type[] MethodInputType = new Type[1];
                MethodInputType[0] = typeof(string);
                MethodInfo ParseMethod = OutputType.GetMethod("Parse", MethodInputType);
                if (ParseMethod != null)
                {
                    object Item = OutputType.Assembly.CreateInstance(OutputType.FullName);
                    object[] Values = new object[1];
                    Values[0] = Input;
                    return ParseMethod.Invoke(Item, Values);
                }
                return null;
            }
            catch { throw; }
        }

In this case, since we don't know what we're parsing the string into, we need to know the output type. From there we simply look for the Parse function and assuming it's there pass in the string to be parsed. Really simple and allows us to parse the string into pretty much anything. Of course it's better if you simply know the type beforehand and just call int.Parse or float.Parse... But for the instances where you can't do that, this function will work in a pinch. So try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 2/4/2010 at 2:29 PM
Tags: , ,
Categories: C#
Post Information: Permalink | Comments (1) | Post RSSRSS comment feed