Search Unity

Unity C# 3.5 or 2.0? .NET, UnityScript, Mono, CLR, CIL and Assemblies explained!

Discussion in 'Scripting' started by Cameron_SM, Jul 25, 2015.

  1. Cameron_SM

    Cameron_SM

    Joined:
    Jun 1, 2009
    Posts:
    915
    I get questions about these things a lot from students or people new to Unity and C# and there's a lot of misinformation around on the forums, unity answers and even on stack-overflow about this so I though I'd create a thread to help demystify a few things. I think it's worth taking a few moments to understand what's happening with the code we write.

    This is the story of the journey your code takes from being text in a file on your PC through to executing as machine instructions on a target device and the technologies involved in that process.

    Disclaimer: I'm by no means an expert, if you think I have something wrong or can think of a clearer way to explain it please let me know and I'll edit this post accordingly.

    C#, .NET, CLR, CIL what's it all mean?

    Lets start with .NET (dot net). This is a software framework for building applications. It includes a large class library knows as the Framework Class Library (FCL) and language interoperability (the ability for 2 programs such as C# and C++ to interact) across several programming languages. Programs written for the .NET Framework execute in a software environment (as opposed to a hardware environment), known as the Common Language Runtime (CLR), an application virtual machine that provides services such as security, memory management, and exception handling. FCL and CLR together constitute the .NET Framework.

    Unity uses an open source subset of .NET to host the scripting environment for their game engine. We interact with this via the C# and UnityScript code files that we create. The two numbers that most people bring up when talking about Unity and the version of .NET or C# are 2.0 and 3.5, and they are both correct in a sense. Unity supports a subset of the .NET Framework features up to about 3.5, and it runs a CLR 2.0 virtual machine to execute them. The relationship between the .NET framework version and the CLR version is a bit messy and loose, but more on that later.

    The C# version is actually synonymous with the .NET Framework but most people build .NET apps in C# and simply refer to it as C# version X. For the sake of my fingers I'm going to do the same. C# 3.5 will compile to target CLR 2.0. UnityScript also compiles to target CLR 2.0. It does this by compiling both languages into what we call Common Intermediate Language (CIL) bytecode. Note that I said build, nor run. Building the game and running the game are a bit different because you only build games on PC, Mac and soon Linux but you can run a Unity game on many many more platforms.


    The word compile scares me!

    When I say compile I'm referring to the process of assembling different things into a unified source. For example, compiling a list of links to cat videos on YouTube or compiling all of your scripts into a single executable. When you run a Unity game, the code that you write and is later being executed on device is not actually a bunch of loose C# or UnityScript files but an assembled package of CIL bytecode. The process of turning C# and UnityScript code into CIL bytecode is called compiling. A set of scripts compiled into CIL bytecode is called an Assembly. More on assemblies later, lets complete your code's journey form source to execution on device first.

    CIL bytecode is the intermediate language that Unity converts all your C# or UnityScript code into so it can be executed as machine code on device at runtime, aka running your game. The thing that does this nifty runtime interpretation and execution is the CLR Virtual Machine (you could also call it a CIL bytecode interpreter). We use the word bytecode because it's generally not the most human readable form of code and it's not designed to be written by people directly (though some do tweak bytecode by hand cause they're cray cray). This is what some CIL bytecode looks like:

    C#
    Code (CSharp):
    1. public class HelloWorld
    2. {
    3.     public static void SayHi()
    4.     {
    5.         System.Console.WriteLine("Hello, World!");
    6.     }
    7. }
    IL Bytecode
    Code (csharp):
    1. .class public auto ansi beforefieldinit HelloWorld
    2.     extends [mscorlib]System.Object
    3. {
    4.     .method public hidebysig static
    5.         void SayHi () cil managed
    6.     {
    7.         .maxstack 8
    8.         IL_0000: nop
    9.         IL_0001: ldstr "Hello, World!"
    10.         IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    11.         IL_000b: nop
    12.         IL_000c: ret
    13.     }
    14.  
    15.     .method public hidebysig specialname rtspecialname
    16.         instance void .ctor () cil managed
    17.     {
    18.         .maxstack 8
    19.         IL_0000: ldarg.0
    20.         IL_0001: call instance void [mscorlib]System.Object::.ctor()
    21.         IL_0006: nop
    22.         IL_0007: ret
    23.     }
    24. }

    Each platform that Unity supports (Mac, PC, iOS, Android, PS4 etc) has it's own custom CLR Virtual Machine that converts CIL bytecode it into machine instructions and that's what makes your scripts work on all those different devices.

    At this point you might be thinking this is confusing as all heck!. Well, you're not wrong. That's a lot of acronyms and none of them sound even remotely cool or sexy. Blame Microsoft, I do. If it helps, imagine both C# and UnityScript as code classes that implement a common interface, lets call it ICommonLanguage. This interface would let a compiler convert/compile both types of code into CIL bytecode. That's it really. Unity uses a compiler that produces CLR 2.0 compatible CIL bytecode.


    But why all this conversion nonsense?

    Well, computers are complex, and if you want to write some code and have it execute the same way on lots of different platforms and devices with vastly different CPU and system architectures then you really need some middle men so to speak. CIL bytecode is that middle man and the CLR is his translator. No one really knows how to understand that CIL fella, but everyone can understand the beautiful sexy CLR once she's done translating his nonsense.


    Where does Mono enter this toxic mix of acronyms?

    Because the .NET Framework is built by Microsoft it typically only supports Microsoft friendly platforms which excludes iOS, Android and all the Game Consoles. To get around this Unity uses Mono which is a project to produce a cross-platform version of the .NET Framework and it's related technologies. Unity uses a Mono CLR on many platforms for running CIL bytecode. The Mono project CLR that Unity deploys only supports CLR 2.0. As a general rule, Unity can compile C# 3.5 language features into CLR 2.0 compatible CLI bytecode. Many C# 4.0 and above features cannot be compiled to target CLR 2.0, but some can (this is the messy bit I was talking about earlier).


    So what version of C# can I actually use?

    That would be 3.5, with some exceptions and some limitations. If the C# language features you want to use are mostly syntax simplification then maybe they can be compiled to target CLR 2.0. For example, C# 6.0's new null-conditional operators and expression bodied functions and properties can be converted to CLR 2.0 compatible CIL bytecode if the compiler knows what's it's doing (the Visual Studio compiler does, Unity's compiler currently does not):

    Code (CSharp):
    1.  
    2. // C# 3.5 -> CLR 2.0
    3. public string Name { get; set; }
    4. public string FirstName
    5. {
    6.     get { return Name.Split(' ')[0]; }
    7. }
    8. public string LastName
    9. {
    10.     get { return Name.Split(' ')[1]; }
    11. }
    12.  
    13. // C# 6.0 -> CLR 2.0
    14. // Only works in Unity as a .dll built with Visual Studio 2015 or a roslyn compiler
    15. public string Name {get; set; }
    16. public string FirstName => Name.Split(' ')[0];
    17. public string LastName => Name.Split(' ')[1];
    18.  
    The reason the second example can work with the correct compiler is because both end up as the exact same set of CIL bytecode instructions when compiled, as long as the compiler understands the source code.

    If you're using Visual Studio and building a C# DLL for use in Unity you can simply specify the CLR target (2.0) in the project settings and it will automatically take care of supporting as many features of the C# language features as it can (up to C# 6.0 in Visual Studio 2015).

    Also keep in mind that because the CLR that's running your compiled code on device is probably one of the Mono Project CLRs then it can have limitations. There are basically two different modes that these Mono CLRs operate in, Ahead of Time (AOT) compiled or Just in Time (JIT) compiled. Getting into the specifics of this is way out of scope but it basically means that in AOT mode the CLR is prevented from doing tricky things that would generate new CIL bytecode at runtime. AOT is only used on iOS, maybe Android?. Everything else uses JIT. Runtime bytecode generation (aka IL injection) means that compiling expression trees and anything using Reflection.Emit will fail. This generally isn't a problem as these are rarely required features in real time games.


    What was that about Assemblies?

    As mentioned earlier Unity compiles your code into an Assembly. Assemblies are a fairly fundamental unit of deployment for the .NET Framework and they take the form of an executable (.exe) file or dynamic link library (.dll) file. For unity, we're all about dll's. An assembly is how Unity groups up and packages your code for deployment to a device. You can think of an assembly as a collection of types and resources that the CLR needs to execute it. Assemblies typically form a logical unit of functionality and are built to work together. Different assemblies can talk to each other, code in one assembly can even be extended by code in another assembly. Unity itself comes with several pre-compiled dll assemblies. UnityEngine.dll is the main one (this is packaged with your game build) that exposes an API that hooks into the internal C++ Unity game engine. UnityEditor.dll is another (this is not packaged with your game build) that lets you extend the Unity editor itself. The new unity UI is also compiled into a separate DLL assembly.

    Why is this important? Well, unity actually compiles your custom game scripts into 4 different assemblies in the background. It does this so that both C# and UnityScript can talk to one another. This is also related to some of Unity's magic folders. For example, any scripts inside Standard Assets or Plugins are compiled first into a separate Assembly, other code is compiled later. C# and UnityScript are also compiled into separate assembles which gives us 4 assemblies in total: Two first-pass assemblies, one for C# and one for UnityScript, then two regular assemblies. Why does Unity do this? It lets you subclass and extend UnityScript via C# (or vice versa) as long as the parent class lives in Standard Assets and is compiled separately before the extending code is compiled.

    A nice tip for large projects. If you have code that's fairly solid and won't change much move it into the Standard Asserts or Plugins folder to speed up the general recompile time. Unity scans project director trees separately and it won't re-compile first-pass assemblies if nothing in standard-assets or plugins has changed.

    For the curious bears

    For a more detailed reference, see this MSDN page on .NET Framework versions and their comparability with CLR versions and different platforms: https://msdn.microsoft.com/en-us/library/bb822049.aspx

    Then there's IL2CPP, which is something Unity is building that converts CIL bytecode into native C++ code? I believe their plan is to eventually phase out all use of the Mono CLR but I'm not 100% up to speed on this or what impact this will have on the set of supported features that are available to us as Unity engine developers. Best place to start on that is probably here: http://blogs.unity3d.com/2014/05/20/the-future-of-scripting-in-unity/

    That's about it. Feel free to ask questions if something isn't clear and of course suggestions for improvements are welcome.
     
    Last edited: Jul 25, 2015
    abegue, r-kamphuis, Mazyod and 9 others like this.
  2. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    One correction: The C# version and .NET version are not the same. There is no such thing as "C# 3.5". There is C# 3.0, which is what was used with .NET 3.0 and .NET 3.5, and then C# 4.0 which released with .NET 4, C# 5.0 with .NET 4.5, and finally C# 6.0 with .NET 4.6.

    Unity uses a weird bastardized compiler that mostly only supports C# 3, but can compile a few features from C# 4, namely just optional parameters on functions. C# 4 also includes the "dynamic" keyword and keywords for covariance/contravariance on generics which cause Unity's compiler to choke (and which, as you mention, also require .NET 4 so can't be used anyway).

    Also I think some people would get mad about referring to the CLR as a VM but that's mostly technical nitpicking. ;)
     
    Cameron_SM likes this.
  3. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    If we ignore Boo (as usual), there are 8 assemblies: 4 "normal" ones and 4 that keep editor scripts:

    Unity Dependencies.png