If you work in C#, you might see several different ways of constructing a new object around your codebase – constructors and general object initialization.
Constructor:
var t = new Obj(var1, var2, …)
Object initialization:
var t = new Obj() {
Var1 = var1,
Var2 = var2,
Var1 = var1,
Var2 = var2,
…
}
(Or maybe a mix of both)
At my work, I primarily see object initialization used. I continued the practice because, well, it was the style of the code. However, I was curious as to how big the performance impact of object initialization was (it’s going to be slower because it creates the object first with default values), so I created a little test program and checked out the generated assembly.
class Program
{
static void Main(string[] args)
{
TestClass var1 = new TestClass(“hi”, “hello”, “sup”);
TestClass var2 = new TestClass()
{
Data1 = “hi”,
Data2 = “hello”,
Data3 = “sup”
};
}
}
public class TestClass
{
public TestClass()
{
}
public TestClass(string data1, string data2, string data3)
{
Data1 = data1;
Data2 = data2;
Data3 = data3;
}
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
You can view the assembly by starting the program, pause the program, and go to Debug->Windows->Disassembly.
Here’s the assembly for the constructor case:
TestClass var1 = new TestClass(“hi”, “hello”, “sup”);
01252DC5 mov ecx,57E0FB0h
01252DCA call 00EE30F4
01252DCF mov dword ptr [ebp-48h],eax
01252DD2 push dword ptr ds:[3BA2330h]
01252DD8 push dword ptr ds:[3BA2334h]
01252DDE mov edx,dword ptr ds:[3BA232Ch]
01252DE4 mov ecx,dword ptr [ebp-48h]
01252DE7 call 01250D00
01252DEC mov eax,dword ptr [ebp-48h]
01252DEF mov dword ptr [ebp-40h],eax
EAX-EDX are just general data registers. We call our constructor, push the parameters onto the stack, and then move the data into the right place. Simple and clean.
However, here’s the code for the object initialization version:
TestClass var2 = new TestClass()
{
Data1 = “hi”,
Data2 = “hello”,
Data3 = “sup”
};
01252DF2 mov ecx,57E0FB0h
01252DF7 call 00EE30F4
01252DFC mov dword ptr [ebp-4Ch],eax
01252DFF mov ecx,dword ptr [ebp-4Ch]
01252E02 call 01250CF8
01252E07 mov edx,dword ptr ds:[3BA232Ch]
01252E0D mov ecx,dword ptr [ebp-4Ch]
01252E10 cmp dword ptr [ecx],ecx
01252E12 call 01250D10
01252E17 nop
01252E18 mov edx,dword ptr ds:[3BA2330h]
01252E1E mov ecx,dword ptr [ebp-4Ch]
01252E21 cmp dword ptr [ecx],ecx
01252E23 call 01250D20
01252E28 nop
……
Eesh. It generates a lot of extra code just to assign members, including a surprising no-op. Not sure why that’s there, but it was enlightening to see how much more code is generated versus the constructor method.
Now, you’re probably thinking “Well, what’s the harm? Most machines these days can handle a few extra cycles, and my app isn’t fighting for performance!” Well, there’s a few reasons you should use constructors:
- If you have a pretty complex object, and you’re purely initializing many of them using the second technique, this extra binary space and time is gonna add up.
- Constructors provide a good way to enforce proper object construction. A good constructor is going to make sure all required data points are entered.
However, there are good reasons to go about with object initialization:
- Creating a ton of different constructors is tedious and messy. Any time you add a new field, that’s quite a bit more work.
- It looks cleaner code wise. You can see that member X is assigned value Y, and not have to consult the constructor definition.
I also learned that our code base primarily used object initialization because one of the developers was a pretty heavy JavaScript developer, so it makes sense to keep that style. However, now that I’ve learned this, I’m primarily going to go with constructors from now on.