Tuesday, 27 January 2009

Accessing object properties from string representations

Late last week I was looking into sorting a custom POCO (Plain Old CLR Object) collection which derived from System.Collection.ObjectModel.Collection<T>; but initiating the sorting in a crazy way ... passing in a parameter list of object property names in the order in which to sort them.

My initial sorting method had a switch on the property on the singular object which I wanted to get rid of due to you would have to add a new switch statement for each property you wanted to sort on ... no ideal.

private void Sort(string propertyName)
    {
        // convert to List
        List<MyClass> myCurrentClass = Items as List<MyClass>;
        // sort
        if (myCurrentClass != null)
        {
            switch (propertyName)
            {
                case "Name":
                    myCurrentClass.Sort(delegate(MyClass myClassOne, MyClass myClassTwo)
                                 {
                                     return
                                         Comparer<string>.Default.Compare(myClassOne.Name,
                                                                          myClassTwo.Name);
                                 }
                        );
                    break;
                case "Increment":
                    myCurrentClass.Sort(delegate(MyClass myClassOne, MyClass myClassTwo)
                                 {
                                     return
                                        Comparer<int>.Default.Compare(myClassOne.Increment,
                                                                     myClassTwo.Increment);
                                 });
                    break;
            }
        }
    }


After hunting around for a solution and being guided toward reflection but without any luck I decided to take the plunge and post my first ever question on StackOverflow. The community which has grown around the site has some very clever people in it so I thought I'd get at least a couple of replies ... and I had a guess that Jon Skeet would be first (as it's all he seems to do!) and I was right, woo! Anyway, I digress.



Jon suggested that I look into using Type.GetProperty and suggested that the whole concept might get a little icky. By this point I had gone past the point of wanting to put it into the original bit of code I was looking (as it would over complicate matters), but wanted to know how I could get to where I originally wanted to be. I was just about to dive back into the madness of reflection when another StackOverlow member, Frederik Gheysels posted a none generic starting point for me to head towards.



Frederik's idea almost compiled and after having a little re-jig managed to get it compiling and working how I expected it to ...



        private void Sort_version1(string propertyName)
        {
            // convert to List
            List<MyClass> myCurrentClass = Items as List<MyClass>;
            // sort
            if (myCurrentClass != null)
            {
                myCurrentClass.Sort(delegate(MyClass one, MyClass two)
                                        {
                                            PropertyInfo pi = typeof (MyClass).GetProperty(propertyName);
                                            return Comparer.Default.Compare(pi.GetValue(one, null), pi.GetValue(two, null));
                                        });
            }
        }


After looking at this, thinking before I was using the generic comparer and after the comment from Jon about the Generic one I decided that I would try and get to use the generic one and get the switch working from the type of the property ... if I could. So after putting in a break point into different places and inspecting the information I got back from PropertyInfo I decided that I would add a static method on MyClass to which would return true if the property existed, but also then return out to me the property class full name and the property info details (this could definitely be better!)



        public static bool HasDetailAndExtract(string propertyName, out string propertyType, out PropertyInfo propertyInfo)
        {
            PropertyInfo pi = typeof (MyClass).GetProperty(propertyName);
            propertyType = (pi != null) ? pi.PropertyType.FullName : string.Empty;
            propertyInfo = pi;
            return (pi != null);
        }


With this I could be a little defensive with the checking in the Sort method, but also perform the switch on the system type while still using the generic comparers which is what my original aim was.



        private void Sort_version2(string propertyName)
        {
            // convert to list
            List<MyClass> myCurrentClass = Items as List<MyClass>;
            string typeOfProperty;
            PropertyInfo pi;
            // sort
            if ((myCurrentClass != null) && (MyClass.HasDetailAndExtract(propertyName, out typeOfProperty, out pi)))
            {
                switch(typeOfProperty)
                {
                    case "System.String":
                        myCurrentClass.Sort(delegate(MyClass one, MyClass two)
                                                {
                                                    return
                                                        Comparer<string>.Default.Compare(pi.GetValue(one, null).ToString(),
                                                                                         pi.GetValue(two, null).ToString());
                                                });
                        break;
                    case "System.Int32":
                        myCurrentClass.Sort(delegate (MyClass one, MyClass two)
                                                {
                                                    return
                                                        Comparer<int>.Default.Compare(
                                                            Convert.ToInt32(pi.GetValue(one, null)),
                                                            Convert.ToInt32(pi.GetValue(two, null)));
                                                });
                        break;
                    default:
                        throw new NotImplementedException("Type of property not implemented yet");
                }
            }
        }


This all works as expected and overall I'm pretty happy with the implementation. I won't be putting it into the code I was originally writing at work purely due to the fact that this is getting more generic and would be overkill as the implementation I was doing at work was pretty specific but I'm glad I managed to get the result that I set out to get in the first place.



I guess the next challenge, when I've got time, will be to try again in vs2008 / c#3 and see how much more elegant I can get the code looking with the use of lambas and the other fun stuff in .net3.5.



As usual, any thoughts or questions then please comment and I'll reply as soon as possible!

No comments: