home comics writing pictures archive about

2021-12-05 - In IL: C# Classes and Structs

In IL

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.

Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CsClass
{
class Program
{
static void Main(string[] args)
{
}
}
internal class InternalClass { }
public class PublicClass { }
public class NesteeClass
{
private class NestedPrivateClass { }
private protected class NestedPrivateProtectedClass { } //C# 7.2
internal class NestedInternalClass { }
protected class NestedProtectedClass { }
protected internal class NestedProtectedInternalClass { }
public class NestedPublicClass { }
}
public interface IInterface { }
public abstract class AbstractClass { }
public sealed class SealedClass : AbstractClass { }
public static class StaticClass { }
public class DerivedClass : AbstractClass { }
public class InterfaceClass : IInterface { }
public struct PublicStruct { }
public enum PublicEnum { }
}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43

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.

.class private auto ansi beforefieldinit CsClass.InternalClass
extends [mscorlib]System.Object
78
79
.class public auto ansi beforefieldinit CsClass.PublicClass
extends [mscorlib]System.Object
93
94

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.

.class auto ansi nested private beforefieldinit NestedPrivateClass
extends [mscorlib]System.Object
111
112
.class auto ansi nested famandassem beforefieldinit NestedPrivateProtectedClass
extends [mscorlib]System.Object
126
127
.class auto ansi nested assembly beforefieldinit NestedInternalClass
extends [mscorlib]System.Object
141
142
.class auto ansi nested family beforefieldinit NestedProtectedClass
extends [mscorlib]System.Object
156
157
.class auto ansi nested famorassem beforefieldinit NestedProtectedInternalClass
extends [mscorlib]System.Object
171
172
.class auto ansi nested public beforefieldinit NestedPublicClass
extends [mscorlib]System.Object
186
187

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.

.class interface public abstract auto ansi CsClass.IInterface
213

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.

.class public abstract auto ansi beforefieldinit CsClass.AbstractClass
extends [mscorlib]System.Object
217
218

Now let’s look at the SealedClass.

.class public auto ansi sealed beforefieldinit CsClass.SealedClass
extends CsClass.AbstractClass
232
233

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.

.class public abstract auto ansi sealed beforefieldinit CsClass.StaticClass
extends [mscorlib]System.Object
247
248

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.

.class public auto ansi beforefieldinit CsClass.DerivedClass
extends CsClass.AbstractClass
252
253

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.

.class public auto ansi beforefieldinit CsClass.InterfaceClass
extends [mscorlib]System.Object
implements CsClass.IInterface
267
268
269

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.

.class public sequential ansi sealed beforefieldinit CsClass.PublicStruct
extends [mscorlib]System.ValueType
283
284

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?

.class public auto ansi sealed CsClass.PublicEnum
extends [mscorlib]System.Enum
290
291

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: