I've been a programmer for a long time and have recently gotten a job writing C#. I was curious to see if Visual Studio optimized property calls to simple memory moves instead of performing a function call and return. So I wrote a program that had two versions of a 3D point class with a method to compute the magnitude: one version accesses the fields directly and one uses properties. I ran both with 100,000,000 points and they took the same amount of time. But when I use ildasm to look at the generated code, the version using properties seems to use a function call to access the property value. (This is a Release build, so code optimization is turned on.)
My questions:
Is the function call to get_X being optimized to a memory move at runtime? (It would seem so since it takes the same amount of time to execute as direct field references.)
Is there a way, using ildasm or another tool, to see which optimizations occur at runtime?
I have tried running the process without the debugger then attaching to the process but VS2017 says "No disassembly available".
The version that calls private fields directly:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(_x * _x + _y * _y + _z * _z);
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3d::_x
IL_0006: ldarg.0
IL_0007: ldfld float64 CPUTests.Point3d::_x
IL_000c: mul
IL_000d: ldarg.0
IL_000e: ldfld float64 CPUTests.Point3d::_y
IL_0013: ldarg.0
IL_0014: ldfld float64 CPUTests.Point3d::_y
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: ldfld float64 CPUTests.Point3d::_z
IL_0021: ldarg.0
IL_0022: ldfld float64 CPUTests.Point3d::_z
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3d::Abs
The version that calls properties, followed by the get_X
method:
.method public hidebysig instance float64
Abs() cil managed
{
// Code size 47 (0x2f)
.maxstack 8
//000052: return Math.Sqrt(X * X + Y * Y + Z * Z);
IL_0000: ldarg.0
IL_0001: call instance float64 CPUTests.Point3dProperties::get_X()
IL_0006: ldarg.0
IL_0007: call instance float64 CPUTests.Point3dProperties::get_X()
IL_000c: mul
IL_000d: ldarg.0
IL_000e: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0013: ldarg.0
IL_0014: call instance float64 CPUTests.Point3dProperties::get_Y()
IL_0019: mul
IL_001a: add
IL_001b: ldarg.0
IL_001c: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0021: ldarg.0
IL_0022: call instance float64 CPUTests.Point3dProperties::get_Z()
IL_0027: mul
IL_0028: add
IL_0029: call float64 [mscorlib]System.Math::Sqrt(float64)
IL_002e: ret
} // end of method Point3dProperties::Abs
.method public hidebysig specialname instance float64
get_X() cil managed
{
// Code size 7 (0x7)
.maxstack 8
//000016: get { return _x; }
IL_0000: ldarg.0
IL_0001: ldfld float64 CPUTests.Point3dProperties::_x
IL_0006: ret
} // end of method Point3dProperties::get_X
In his book, CLR via C#, Jeffrey Richter writes:
For simple get and set accessor methods, the just-in-time (JIT) compiler inlines the code so that there’s no run-time performance hit as a result of using properties rather than fields.
CLR via C# is kind of my bible, so for me, this is more than enough proof that they are inlined.
ildasm can only show you the end result of the optimizations that happen at compile time. If you want to check the runtime optimizations, you have to actually look at the runtime code, that is, the code that is generated from the assembly and is being run. S.O.S extension for WinDbg might be a tool that can help you.