



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.

Example MyClass that implements IComparable /* * Copyright (c) 2013 CodingBlocks.NET <https://codingblocks.net>, Michael Outlaw <michael@michaeloutlaw.com> * * See the file license.txt for copying permission. */ using System; namespace CodingBlocks.NET { 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); } } } 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>.

Example MyGenericClass that implements IComparable(T) /* * Copyright (c) 2013 CodingBlocks.NET <https://codingblocks.net>, Michael Outlaw <michael@michaeloutlaw.com> * * See the file license.txt for copying permission. */ using System; namespace CodingBlocks.NET { 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)); } } } 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.

Example application that uses MyClass and MyGenericClass /* * Copyright (c) 2013 CodingBlocks.NET <https://codingblocks.net>, Michael Outlaw <michael@michaeloutlaw.com> * * See the file license.txt for copying permission. */ using System; namespace CodingBlocks.NET { class Program { 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(); } } } 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.

Intermediary Language (IL) for Main .method private hidebysig static void Main ( string[] args ) cil managed { // Method begins at RVA 0x215c // Code size 126 (0x7e) .maxstack 2 .entrypoint .locals init ( [0] class CodingBlocks.NET.MyClass mc, [1] class CodingBlocks.NET.MyGenericClass mgc, [2] class [mscorlib]System.Exception ex, [3] class CodingBlocks.NET.MyClass '<>g__initLocal0', [4] class CodingBlocks.NET.MyGenericClass '<>g__initLocal1' ) IL_0000: nop IL_0001: newobj instance void CodingBlocks.NET.MyClass::.ctor() IL_0006: stloc.3 IL_0007: ldloc.3 IL_0008: ldc.i4.s 100 IL_000a: callvirt instance void CodingBlocks.NET.MyClass::set_MyData(int32) IL_000f: nop IL_0010: ldloc.3 IL_0011: stloc.0 IL_0012: newobj instance void CodingBlocks.NET.MyGenericClass::.ctor() IL_0017: stloc.s '<>g__initLocal1' IL_0019: ldloc.s '<>g__initLocal1' IL_001b: ldc.i4 200 IL_0020: callvirt instance void CodingBlocks.NET.MyGenericClass::set_MyData(int32) IL_0025: nop 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 .try { IL_003e: nop IL_003f: ldloc.0 IL_0040: ldstr "5" IL_0045: callvirt instance int32 CodingBlocks.NET.MyClass::CompareTo(object) IL_004a: pop IL_004b: nop IL_004c: leave.s IL_005f } // end .try catch [mscorlib]System.Exception { IL_004e: stloc.2 IL_004f: nop IL_0050: ldloc.2 IL_0051: callvirt instance string [mscorlib]System.Exception::get_Message() IL_0056: call void [mscorlib]System.Console::WriteLine(string) IL_005b: nop IL_005c: nop IL_005d: leave.s IL_005f } // end handler IL_005f: nop IL_0060: ldloc.1 IL_0061: ldstr "5" IL_0066: callvirt instance int32 CodingBlocks.NET.MyGenericClass::CompareTo(string) IL_006b: pop IL_006c: ldstr "Press ENTER to quit..." IL_0071: call void [mscorlib]System.Console::WriteLine(string) IL_0076: nop IL_0077: call string [mscorlib]System.Console::ReadLine() IL_007c: pop IL_007d: ret } // end of method Program::Main 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 . i 4 . 5 IL_002b : box [ mscorlib ] System . Int 32 IL_0030 : callvirt instance int 32 CodingBlocks . NET . MyClass :: CompareTo ( object ) IL_0035 : pop IL_0036 : ldloc . 1 IL_0037 : ldc . i 4 . 5 IL_0038 : callvirt instance int 32 CodingBlocks . NET . MyGenericClass :: CompareTo ( int 32 ) 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.

Intermediary Language (IL) for MyClass::CompareTo .method public final hidebysig newslot virtual instance int32 CompareTo ( object obj ) cil managed { // Method begins at RVA 0x2070 // Code size 85 (0x55) .maxstack 2 .locals init ( [0] int32 i, [1] int32 CS$1$0000, [2] bool CS$4$0001, [3] int32 CS$0$0002 ) IL_0000: nop IL_0001: ldarg.1 IL_0002: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() IL_0007: ldarg.0 IL_0008: call instance int32 CodingBlocks.NET.MyClass::get_MyData() IL_000d: box [mscorlib]System.Int32 IL_0012: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType() IL_0017: call bool [mscorlib]System.Type::op_Inequality(class [mscorlib]System.Type, class [mscorlib]System.Type) IL_001c: ldc.i4.0 IL_001d: ceq IL_001f: stloc.2 IL_0020: ldloc.2 IL_0021: brtrue.s IL_002f IL_0023: nop IL_0024: ldstr "obj is not an Int32!" IL_0029: newobj instance void [mscorlib]System.ArgumentException::.ctor(string) IL_002e: throw 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 IL_0053: ldloc.1 IL_0054: ret } // end of method MyClass::CompareTo 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 . Int 32 IL_0040 : stloc . 0 IL_0041 : ldarg . 0 IL_0042 : call instance int 32 CodingBlocks . NET . MyClass :: get _ MyData ( ) IL_0047 : stloc . 3 IL_0048 : ldloca . s CS $0 $0002 IL_004a : ldloc . 0 IL_004b : call instance int 32 [ mscorlib ] System . Int 32 :: CompareTo ( int 32 ) IL_0050 : stloc . 1 IL_0051 : br . s IL _ 0053

But we don’t see an unbox operation in MyGenericClass::CompareTo.

Intermediary Language (IL) for MyGenericClass::CompareTo(Int32) .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 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.