Recently, we’ve discussed the adverse effects that boxing and unboxing can have on the performance of your application. Furthermore, during episode two, we mentioned the unintentional use of boxing when using interfaces and how using generic interfaces instead can help.
But how?
Let’s consider the following example class with my old friend IComparable.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class MyClass : IComparable { public Int32 MyData { get; set; } public int CompareTo(Object obj) { if (obj.GetType() != MyData.GetType()) { throw new ArgumentException("obj is not an Int32!"); } Console.WriteLine("MyClass.CompareTo(object) - non-Generic, obj is a boxed Int32."); var i = (Int32)obj; // Unboxing return MyData.CompareTo(i); } } |
This is a simple class that stores some data, MyData, and provides a comparison method, CompareTo, that will allow it be sorted in a List for example. Notice that this class implements the non-generic IComparable.
Let’s also consider this example class, but notice that it implements the generic version, IComparable<T>.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class MyGenericClass : IComparable<Int32>, IComparable<String> { public Int32 MyData { get; set; } public int CompareTo(Int32 other) { Console.WriteLine("MyGenericClass.CompareTo(Int32) - Int32 Generic, other is a value type!"); return MyData.CompareTo(other); } public int CompareTo(String other) { Console.WriteLine("MyGenericClass.CompareTo(String) - String Generic, other is a reference type!"); return CompareTo(Int32.Parse(other)); } } |
This class, like MyClass, stores some data, MyData, and provides some comparison operations. But is this just syntactic sugar or are we actual gaining anything?
In short, we’re gaining a lot. Let’s look at an example of each class being used below to see why.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
static void Main(string[] args) { var mc = new MyClass { MyData = 100 }; var mgc = new MyGenericClass { MyData = 200 }; mc.CompareTo(5); // 5 is boxed mgc.CompareTo(5); // 5 is not boxed try { mc.CompareTo("5"); // Oops! We're expecting an int! } catch (Exception ex) { Console.WriteLine(ex.Message); } mgc.CompareTo("5"); // This is convenient Console.WriteLine("Press ENTER to quit..."); Console.ReadLine(); } |
Nothing up our sleeves here, just a simple instance of each class and we’ll compare each to 5. Rather than just trusting the comments to the side, let’s use ILSpy to actually see what’s happening.
33 34 35 36 37 38 39 40 41 42 43 |
IL_0026: ldloc.s '<>g__initLocal1' IL_0028: stloc.1 IL_0029: ldloc.0 IL_002a: ldc.i4.5 IL_002b: box [mscorlib]System.Int32 IL_0030: callvirt instance int32 CodingBlocks.NET.MyClass::CompareTo(object) IL_0035: pop IL_0036: ldloc.1 IL_0037: ldc.i4.5 IL_0038: callvirt instance int32 CodingBlocks.NET.MyGenericClass::CompareTo(int32) IL_003d: pop |
Right before the call to MyClass::CompareTo we see that a box operation happens, however, we don’t see that before MyGenericClass::CompareTo.
And if we dig futher with ILSpy into MyClass::CompareTo, we’ll see an unbox operation as a result of our (Int32) casting operation.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
IL_002f: ldstr "MyClass.CompareTo(object) - non-Generic, obj is a boxed Int32." IL_0034: call void [mscorlib]System.Console::WriteLine(string) IL_0039: nop IL_003a: ldarg.1 IL_003b: unbox.any [mscorlib]System.Int32 IL_0040: stloc.0 IL_0041: ldarg.0 IL_0042: call instance int32 CodingBlocks.NET.MyClass::get_MyData() IL_0047: stloc.3 IL_0048: ldloca.s CS$0$0002 IL_004a: ldloc.0 IL_004b: call instance int32 [mscorlib]System.Int32::CompareTo(int32) IL_0050: stloc.1 IL_0051: br.s IL_0053 |
But we don’t see an unbox operation in MyGenericClass::CompareTo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
.method public final hidebysig newslot virtual instance int32 CompareTo ( int32 other ) cil managed { // Method begins at RVA 0x20fc // Code size 32 (0x20) .maxstack 2 .locals init ( [0] int32 CS$1$0000, [1] int32 CS$0$0001 ) IL_0000: nop IL_0001: ldstr "MyGenericClass.CompareTo(Int32) - Int32 Generic, other is a value type!" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ldarg.0 IL_000d: call instance int32 CodingBlocks.NET.MyGenericClass::get_MyData() IL_0012: stloc.1 IL_0013: ldloca.s CS$0$0001 IL_0015: ldarg.1 IL_0016: call instance int32 [mscorlib]System.Int32::CompareTo(int32) IL_001b: stloc.0 IL_001c: br.s IL_001e IL_001e: ldloc.0 IL_001f: ret } // end of method MyGenericClass::CompareTo |
So, in this basic example we can already see that by using generics we can save our applications from calling unnecessary operations. Imagine how much processing time and memory might be used if we were processing large lists of data.
But I mentioned we’d gain a lot. What else have we gained?
Let’s go back to Main and look at the try/catch. With the non-generic version of IComparable we lose type safety at code/compile time. This is a side effect of everything inheriting from Object. Because the String “5” can safely be cast as an Object, the compiler is OK with this operation. However, our class, MyClass, is not set up to handle this data type. Sure, we’re trapping for it by comparing the type of obj with MyData, but wouldn’t it be nice not to have to worry about it.
This is one of the many benefits that generics provide. Not only do I get to specify which types I want MyGenericClass to support, in this case Int32 and String, but I provide type safety to the users of my class while they are coding/compiling meaning that problems can be presented as compile errors instead of run-time errors which can be more difficult to find. And that can prove invaluable.
Conclusion
In summary, if you have the option to use a generic version of an interface instead of a non-generic version, it’s a no brainer: use the generic version. Not only will you benefit from the better performance within your application but your colleagues will appreciate the ease of use of your classes.
We hope you enjoyed reading this and we’d love to hear your feedback. Drop us a question, comment, or rant at comments@codingblocks.net. Download the examples discussed in this article here.