Refactoring with reflection

There are often times when you need to do a refactoring, that Resharper cannot help you with. In my last post I described how regular expressions can be useful: Static refactoring with Visual Studio regular expressions

This time the case is different and simple replacement would not work in this case.

The default value for a property

I came across the code, where the DefaultValue attribute was heavily used, but actually, not like it should be. The DefaultValue attribute provided by the .Net Framework is useful for code generators to see if the property default value is the same as the current value and if the code for that property should be generated. I don’t want to get into details, but you can have a look at this article for more information.

In my case, I needed to change all the default values to assignments of properties when the class was created. The problem was that this class had more than 1000 properties like this and doing it manually would not only take a lot of time but could potentially introduce bugs.

Let’s have a look at the sample code:

    public class DefaultSettings
    {
        public DefaultSettings()
        {
            // assignments should be here
        }

        [DefaultValue(3)]
        public int LoginNumber { get; set; }

        [DefaultValue("PrimeHotel")]
        public string HotelName { get; set; }

        [DefaultValue("London")]
        public string Town { get; set; }

        [DefaultValue("Greenwod")]
        public string Street { get; set; }

        [DefaultValue("3")]
        public string HouseNumber { get; set; }

        [DefaultValue(RoomType.Standard)]
        public RoomType Type { get; set; }
    }

This is where reflection comes in handy. I could easily identify the attributes on properties for a given type, but how to change that into code?

Let’s write a unit test! A unit test is a small piece of code that can use almost any class from the tested project and what’s the most important – can be easily run. 💪

    [TestFixture]
    public class PropertyTest
    {
        [Test]
        public void Test1()
        {
            var prop = GetProperties();

            Assert.True(true);
        }

        public string GetProperties()
        {
            var sb = new StringBuilder();

            PropertyDescriptorCollection sourceObjectProperties = TypeDescriptor.GetProperties(typeof(DefaultSettings));

            foreach (PropertyDescriptor sourceObjectProperty in sourceObjectProperties)
            {
                var attribute = (DefaultValueAttribute)sourceObjectProperty.Attributes[typeof(DefaultValueAttribute)];

                if (attribute != null)
                {
                    // produce a string
                }
            }

            return sb.ToString();
        }
    }

It’s a simple test that just runs GetProperties method. This method fetches PropertyDescriptorCollection that represents all properties in DefaultSettings class. Then for each of those, we check if they contain DefaultValueAttribute. Now we just need to somehow generate code for every property and write it to StringBuilder. It’s actually simpler than it sounds.

Let’s check how code will behave differently for enums, strings, and other types:

public string GetProperties()
{
    var sb = new StringBuilder();

    PropertyDescriptorCollection sourceObjectProperties = TypeDescriptor.GetProperties(typeof(DefaultSettings));

    foreach (PropertyDescriptor sourceObjectProperty in sourceObjectProperties)
    {
        var attribute = (DefaultValueAttribute)sourceObjectProperty.Attributes[typeof(DefaultValueAttribute)];

        if (attribute != null)
        {
            if (sourceObjectProperty.PropertyType.IsEnum)
            {
                sb.AppendLine($"{sourceObjectProperty.Name} = {sourceObjectProperty.PropertyType.FullName.Replace("+", ".")}.{attribute.Value};");
            }
            else if (sourceObjectProperty.PropertyType.Name.Equals("string", StringComparison.OrdinalIgnoreCase))
            {
                sb.AppendLine($"{sourceObjectProperty.Name} = \"{attribute.Value}\";");
            }
            else
            {
                var value = attribute.Value == null ? "null" : attribute.Value;
                sb.AppendLine($"{sourceObjectProperty.Name} = {value};");
            }
        }
    }

    return sb.ToString();
}

For an enum, we need to provide a name with namespace and an enum value after a dot. For strings, we need to add apostrophes and for nullable types, we need to provide null. For others – we just assign the attribute value.

Are you curious if that would work? 🤔

Not too bad, not too bad at all 🙂 

If I paste that into my constructor, it will look like this:

    public DefaultSettings()
    {
        LoginNumber = 3;
        HotelName = "PrimeHotel";
        Town = "London";
        Street = "Greenwod";
        HouseNumber = "3";
        Type = PrimeHotel.Web.Models.RoomType.Standard;
    }

Guess what? It works!

The summary

Reflection is not considered as the best practice but it is a very powerful tool. In this example, I showed how to use it and invoke that code in a very simple way – as a unit test. It’s not the code that you would commit, but for a try and error process, it’s great. If you came across a manual task like this in your code, try to hack it 😎

I hope that you learned something with me today. If so, happy days 🍺

Don’t forget to subscribe to the newsletter to get notifications about my next posts. 📣

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *