Both Smalltalk classes and metaclasses are represented by .NET classes. All Smalltalk classes inherit from a Root class that is a subclass of the .NET System.Object class. Smalltalk instance variables correspond directly to .NET instance variables, and Smalltalk class instance variables are represented by .NET instance variables on the .NET class corresponding to the Smalltalk metaclass. Smalltalk class variables are represented by .NET static variables and are in the .NET class corresponding to the class not the metaclass.
As an example consider the Smalltalk classes:
In .NET these would be represented by the following:
Solid lines are inheritance relationships and dashed lines are #class pointers. Unlike most Smalltalk implementations, there isn't a Metaclass class.
Methods are compiled directly to .NET CIL virtual functions. Most Smalltalk syntax is directly supported by CIL, but there are some things that are lacking. In particular, literals and block closures are the two main items. To support literals the Smalltalk pool object contains static variables that contain arrays of all types of literals. These static variable arrays are initialized when the program starts.
Block closures are more difficult. All non-optimized blocks are compiled into specially created classes. Each class will have an instance variable for each variable that the block references but does not define. When the block is created it will pass in all of the external variable references. For example, if you compile:
^someCollection detect: [:each | each = anObject] ifNone: 
It will create two classes for the two blocks. The first block references but doesn't not define "anObject", therefore "anObject" will be an instance variable of the new block class. The second block has no external references so it's .NET class won't have any instance variables. If this method is compiled, it will generate bytecodes similar to:
load instance variable "someCollection"
push argument "anObject"
create instance of Block1 with "anObject" as the parameter for the constructor
create instance of Block2 with no parameters
call virtual function for #detect:ifNone:
Blocks that have returns are even more difficult. .NET has no support for non-local returns so #Smalltalk uses exceptions to simulate non-local returns. When a method that has a non-local return is entered, the method generates a number that is used by the exception that is thrown. When a return exception is thrown with that generated number, the method handles that exception and returns the value of the return exception. If the generated number doesn't match, then the return exception is passed to outer handlers. Since exceptions are used to simulate non-local returns, non-local returns are much slower in #Smalltalk than other Smalltalks.