Why is reflection faster than object Initialization?

Viewed 101
  1. Sorry for non computer science terms (self taught)
  2. Sorry incorrect terminology (Anyone willing to edit, please do, (self taught))

I searched as much as I could, with my limited knowledge of terminology for what I was searching for, but I could not find and answer to the question I was after.

I was surprised to find that filling an object with data was faster through Reflection than using object Initialization.

I made a test console app for this purpose. First the test class,

class TestClass
    {
        public string PropertyOne { get; set; }
        public string PropertyTwo { get; set; }
        public string PropertyThree { get; set; }
        public string PropertyFour { get; set; }
        public string PropertyFive { get; set; }
        public string PropertySix { get; set; }
        public string PropertySeven { get; set; }
        public string PropertyEight { get; set; }
        public string PropertyNine { get; set; }
        public string PropertyTen { get; set; }
    }
}

Then the methods to get data, First is reflection,

public static void ReflectionTest()
    {
        for (int i = 0; i < 10000000; i++)
        {
            TestClass testClass = new TestClass();
            Type type = testClass.GetType();
            PropertyInfo[] properties = type.GetProperties();
            foreach (var propertyInfo in properties)
            {
                switch (propertyInfo.Name)
                {
                    case nameof(testClass.PropertyOne):
                        propertyInfo.SetValue(testClass, "PropertyOne" + i);
                        break;
                    case nameof(testClass.PropertyTwo):
                        propertyInfo.SetValue(testClass, "PropertyTwo" + i);
                        break;
                    case nameof(testClass.PropertyThree):
                        propertyInfo.SetValue(testClass, "PropertyThree" + i);
                        break;
                    case nameof(testClass.PropertyFour):
                        propertyInfo.SetValue(testClass, "PropertyFour" + i);
                        break;
                    case nameof(testClass.PropertyFive):
                        propertyInfo.SetValue(testClass, "PropertyFive)" + i);
                        break;
                    case nameof(testClass.PropertySix):
                        propertyInfo.SetValue(testClass, "PropertySix" + i);
                        break;
                    case nameof(testClass.PropertySeven):
                        propertyInfo.SetValue(testClass, "PropertySeven" + i);
                        break;
                    case nameof(testClass.PropertyEight):
                        propertyInfo.SetValue(testClass, "PropertyEight" + i);
                        break;
                    case nameof(testClass.PropertyNine):
                        propertyInfo.SetValue(testClass, "PropertyNine" + i);
                        break;
                    case nameof(testClass.PropertyTen):
                        propertyInfo.SetValue(testClass, "PropertyTen" + i);
                        break;
                }                    
            }
            TestClasses.Add(testClass);
        }
    }

Next is Initialization of Object,

public static void InitializationTest()
    {
        for (int i = 0; i < 10000000; i++)
        {
            TestClass testClass = new TestClass
            {
                PropertyOne = "PropertyOne" + i,
                PropertyTwo = "PropertyTwo" + i,
                PropertyThree = "PropertyThree" + i,
                PropertyFour = "PropertyFour" + i,
                PropertyFive = "PropertyFive)" + i,
                PropertySix = "PropertySix" + i,
                PropertySeven = "PropertySeven" + i,
                PropertyEight = "PropertyEight" + i,
                PropertyNine = "PropertyNine" + i,
                PropertyTen = "PropertyTen" + i
            };
            TestClasses.Add(testClass);
        }
    }

And the test code

static List<TestClass> TestClasses { get; set; } = new List<TestClass>();

    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        ReflectionTest();
        Console.WriteLine($"Reflection Test time: {sw.Elapsed}");
        sw.Reset();
        sw.Start();
        InitializationTest();
        Console.WriteLine($"Initialization Test time: {sw.Elapsed}");
        sw.Stop();
    }

Using this code reflection was 20% faster using object Initialization. What is the reason for this?

EDIT: Adding TestClasses.Clear(); to the code shows that 'object Initialization' is nearly twice as fast. Thanks for the replies and comments.

2 Answers

Such measurements don't make sense - otherwise everyone would use reflection instead of object instances.

The main reason why results are confusing is that all items are being added to the same List. Invoke tests in different order and InitializationTest performs better.

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();

    sw.Start();
    InitializationTest();
    Console.WriteLine($"Initialization Test time: {sw.Elapsed}");
    sw.Stop();
    sw.Reset();

    sw.Start();
    ReflectionTest();
    Console.WriteLine($"Reflection Test time: {sw.Elapsed}");
    sw.Stop();
    sw.Reset();

    Console.ReadLine();
}

As Matthew Watson put in a comment: "better still, don't use the lists at all as part of the timing. We're supposed to be timing how long it takes to create object - not how long it takes to fill a very large list."


Reflection test included unnecessary costs: repeated GetProperties invokations, testing conditions in switch

public static void ReflectionTest()
{
    PropertyInfo[] properties = typeof(TestClass).GetProperties();
    for (int i = 0; i < 10000000; i++)
    {
        TestClass testClass = new TestClass();
        foreach (var propertyInfo in properties)
        {
            propertyInfo.SetValue(testClass, propertyInfo.Name + i);
        }
        TestClasses.Add(testClass);
    }
}

Benchmarking is hard. Always use BenchmarkDotNet.

I rewrote your test using BenchmarkDotNet, and got these results:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-3317U CPU 1.70GHz (Ivy Bridge), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.0.100-preview6-012264
  [Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.30373, CoreFX 4.700.19.30308), X64 RyuJIT
  DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.30373, CoreFX 4.700.19.30308), X64 RyuJIT
|     Method |        Mean |     Error |    StdDev |
|----------- |-------------|-----------|-----------|
|   Instance |    41.61 ns |  0.309 ns |  0.274 ns |
| Reflection | 3,321.40 ns | 28.379 ns | 25.157 ns |

This clearly shows that your reflection code is around 80 times slower than using an object initializer.

@ASh's answer talks about why your benchmark is flawed, so I won't replicate that here - see their answer.

Benchmark code:

public class Benchmark
{
    [Benchmark]
    public TestClass Instance()
    {
        TestClass testClass = new TestClass
        {
            PropertyOne = "PropertyOne",
            PropertyTwo = "PropertyTwo",
            PropertyThree = "PropertyThree",
            PropertyFour = "PropertyFour",
            PropertyFive = "PropertyFive)",
            PropertySix = "PropertySix",
            PropertySeven = "PropertySeven",
            PropertyEight = "PropertyEight",
            PropertyNine = "PropertyNine",
            PropertyTen = "PropertyTen"
        };
        return testClass;
    }

    [Benchmark]
    public TestClass Reflection()
    {
        TestClass testClass = new TestClass();
        Type type = testClass.GetType();
        PropertyInfo[] properties = type.GetProperties();
        foreach (var propertyInfo in properties)
        {
            switch (propertyInfo.Name)
            {
                case nameof(testClass.PropertyOne) :
                    propertyInfo.SetValue(testClass, "PropertyOne");
                    break;
                case nameof(testClass.PropertyTwo) :
                    propertyInfo.SetValue(testClass, "PropertyTwo");
                    break;
                case nameof(testClass.PropertyThree) :
                    propertyInfo.SetValue(testClass, "PropertyThree");
                    break;
                case nameof(testClass.PropertyFour) :
                    propertyInfo.SetValue(testClass, "PropertyFour");
                    break;
                case nameof(testClass.PropertyFive) :
                    propertyInfo.SetValue(testClass, "PropertyFive");
                    break;
                case nameof(testClass.PropertySix) :
                    propertyInfo.SetValue(testClass, "PropertySix");
                    break;
                case nameof(testClass.PropertySeven) :
                    propertyInfo.SetValue(testClass, "PropertySeven");
                    break;
                case nameof(testClass.PropertyEight) :
                    propertyInfo.SetValue(testClass, "PropertyEight");
                    break;
                case nameof(testClass.PropertyNine) :
                    propertyInfo.SetValue(testClass, "PropertyNine");
                    break;
                case nameof(testClass.PropertyTen) :
                    propertyInfo.SetValue(testClass, "PropertyTen");
                    break;
            }
        }
        return testClass;
    }
}

class Program
{
    public static void Main()
    {
        var summary = BenchmarkRunner.Run(typeof(Program).Assembly);
    }
}
Related