2021-12-05 - In IL: C# Classes and Structs
In IL
- Part 1 - Introduction
- Part 2 - Variables and Types
- Part 3 - Variables in Visual Basic .NET
- Part 4 - Instructions and the Stack
- Part 5 - Volume of a Cylinder (Operations)
- Part 6 - Branching Instructions
- Part 7 - Largest of Two Numbers (if-else)
- Part 8 - Largest of Three Numbers (If-ElseIf-Else)
- Part 9 - Switch instruction
- Part 10 - Grade Analyser (switch)
- Part 11 - Prize Calculator (switch-2)
- Part 12 - VB Grade Analyser (Select)
- Part 13 - Loop Instructions
- Part 14 - Print the Alphabet (while)
- Part 15 - Print the Alphabet (do while, for)
- Part 16 - Print the Alphabet (Do Until)
- Part 17 - Print the Alphabet (break, continue)
- Part 18 - Array Instructions
- Part 19 - Summing Arrays
- Part 20 - Other Instructions
- Part 21 - Assemblies
- Part 22 - Class Definitions
- Part 23 - C# Classes and Structs
- Part 24 - VB Classes, Modules and Structures
- Part 25 - Field Definitions
- Part 26 - Field Declarations
Last time we looked at class definitions in IL, now let’s see how some compiled C# examples look. This program has a variety of class and struct definitions.
If we compile this we get a lot of code but let’s just focus on the classes starting with the internal and public classes.
InternalClass is marked as private and PublicClass is marked as public these correspond to the Visibility options we saw last time. Now let’s look at the nested classes.
NestedPrivateClass is marked as nested private, NestedPrivateProtectedClass is marked as nested famandassem, NestedInternalClass is marked as nested assembly, NestedProtectedClass is marked as nested family, NestedProtectedInternalClass is marked as nested famorassemand NestedPublicClass is marked as nested public. These correspond to the Accessibility options. Note that on a nested class internal is compiled as nested assembly but on the non-nested class it’s private. Also note how private protected and protected internal get compiled down to famandassem and famorassem respectivly. C# is trying to limit its keyword usage which can lead to confusing situations where as IL is trying to be much more explicit and clear in what it’s doing. Trade-offs of the differing goals of the two languages. Now let’s look at the interface.
IInterface is also declared using the .class directive but it has the interface attribute applied to it to mark it as an interface. It’s also marked as abstract which means you can’t declare an instance of this class, which makes sense for an interface. We can also see that option if we look at AbstractClass.
Now let’s look at the SealedClass.
It's marked with the sealed attribute which means you can’t derive another class from it. So far these examples have mapped fairly easily from C# to IL but how does StaticClass map? There’s no static IL attribute. Well let’s see.
It combines several attributes to achieve the desired result. We have the abstract attribute to prevent instances of that type from being declared but it's also marked as sealed so you can't derive a class from it and declare an instance of that. So by combining these two attributes the C# compiler has created it's idea of static without that needing to be specifically specified in IL. Now let’s look at DerivedClass.
It's marked with extends CsClass.AbstractClass. This is how base classes are specified in IL. Note how all the other classes, except IInterface, are marked as extending [mscorlib]System.Object. This is how everything can be treated as a derivation of Object because they are. This is implicit when defining C# classes but explicit in the generated IL. Now let’s see how InterfaceClass works.
It's also marked as extending Object but it has an aditional implements CsClass.IInterface which is how IL specifies the interfaces a class implements. Again notice the shift from implicit to explicit. In C# you simply list the base class and interfaces separated by commas but in IL you say it extends these base classes and implements these interfaces. Back to things that don’t seem to have options in IL, how do structs work? Well let’s see.
PublicStruct is also declared using .class but instead of extending from Object we have extends [mscorlib]System.ValueType. System.ValueType is a special class in IL that tells the runtime to treat the class as a value type which means that instead of references being passes around the actual value is passed around. Also note that it’s marked as sequential instead of auto. This is because the default behaviour for structs is to have the fields laid out in memory the same way they are declared in the struct. But what about enums?
PublicEnum has extends [mscorlib]System.Enum with System.Enum being another special class. Note that System.Enum derives from System.ValueType which derives from System.Object so everything is still objects in the end.
So as you can see everything in IL is actually a class. Some classes just have more options applied than others. Now a quick note about namespaces, notice that there aren’t any namespace declarations. Whenever we declare a class or reference a class we use it’s fully qualified name. Namespaces are a C# concept for making it easier to specify these names but they don’t really exist at the IL level. IL is meant for computers and computers don’t mind a little extra typing.
Next time we’re going to look at Visual Basic .NET classes, structures and modules. I bet you can guess how those are going to look once compiled.
Comments: