C# Crash Course

Fully teaching C# or introducing a new comer to programming in general is out of the scope of this documentation, so this will page will assume you already have experience with another language and are looking for a place to quickly understand C#.

C# is a very complicated language with a vast array of features and this page will only be scratching the very tip of the iceburg.

So, what is C#?

C# is an object orientated programming language with an event driven paradigm. It’s actually fairly similar to Java in it’s structure, but with C based syntax instead. If you come from a Java or C++ background then C# should be easy enough to get a grasp of.

Basic Keywords

char    // character
string  // an array of characters

byte    // unsigned byte
sbyte   // signed byte

int     // signed 32-bit integer
uint    // unsigned 32-bit integer
short   // signed 16-bit integer
ushort  // unsigned 16-bit integer
long    // signed 64-bit integer
ulong   // unsigned 64-bit integer

float   // 32-bit floating point value
double  // 64-bit floating point value

null    // null reference
void    // used as a return type on methods that don't return anything

Access Modifiers

Much like in languages similar to C#, any kind of object can have access modifiers that determine what can access what in your code. Here’s a list of them:

public      // anything can access the object
internal    // only code inside of your library can access the object
private     // only the root class that defines the object can access it
protected   // object is private but can be accessed by children of a root class
sealed      // class is public but cannot be inherited

static      // object is always in memory and does not require an object instance to access.
const       // object is static but cannot be modified at runtime

Class Example

This example shows you a fully structured class that makes use of many C# features. I have commented everything to explain what’s going on.

There are also structs in C#, which you can think of as more lightweight classes. They’re a lot trickier to understand then classes though, so I recommend you read Microsoft’s documentation if you’re interested in that.

// double forward slash is a single line comment

/* Forward slash with an asterix is a multiline comment


See, even this is a comment!
*/

// This allows us to reference code from other namespaces (mentioned below).
using System.Collections.Generic;
using UnityEngine;

// This is a namespace. They're not required but they let us organise our code.
// You can think of namespaces like a folder hiearchy.
// Not wrapping a class in a namespace puts it into the "root".
// Everything can access anything in the root without a using to reference it.
// By using a period we can define sub directories, so for this MyClass is in
//   Example -> Crash Course
namespace Example.CrashCourse
{
    public class MyClass
    {
        // an example of a static variable. This can be accessed via MyClass.CoolFeatureEnabled
        public static bool CoolFeatureEnabled = true;

        // an example of a constant. This can be accessed via MyClass.DebugMode.
        public const bool DebugMode = true;

        public MyClass()
        {
            // this is a constructor
            // called on new instances of the class when created
            // I'm including this to show you how to declare one. They're not required

            if (DebugMode)
            {
                Debug.Log("Hello World!");
            }
        }

        public MyClass(int myNumber)
        {
            // this is a constructor with parameters
            // called on new instances of the class when created
            // I'm including this to show you how to declare one. They're not required

            MyNumber = myNumber;

            if (DebugMode)
                Debug.Log("Hello World!");

            // we can indent code on the next line to nest it too
        }

        ~MyClass()
        {
            // this is a destructor
            // called when the class instance is about to be destroyed
            // I'm including this to show you how to declare one. They're not required

            if (DebugMode) Debug.Log("Goodbye World!");

            // nested code can actually be on the same line too if you want tidy one liners
        }

        // an example of a public variable
        public int MyNumber = 2159;

        // an example of a private variable
        private bool _myBool = true;

        // an example of an array that holds the numbers 1 through 5
        public int[] MyArray = new int[5] {1, 2, 3, 4, 5};

        // an example of a new list. We can leave this empty for now.
        public List<int> MyList = new List<int>();

        /* an example of a property

            You can think of these like a method that are specifically designed to handle
            controlling data in the class. This is a really basic example of how we can use
            one to get and set our private _myBool variable.

            If you get rid of the set portion, you can use this to expose _myBool as a readonly
            variable.
        */
        public bool MyBool
        {
            get {return _myBool;)
            set {_myBool = value;}
        }

        // example of a method that doesn't return anything
        // let's have some fun with loops in this!
        public void PopulateList()
        {
            // make sure MyList isn't null and redeclare it if it is.
            // since we declared it above it won't be, but this is a good practice anyway
            if (MyList == null) MyList = new List<int>();

            // using a for loop to enumerate from 0 to 10 and add the value to the list
            for (int i = 0; i < 10; ++i)
            {
                MyList.add(i);
            }

            if (DebugMode)
            {
                // using a foreach loop to enumerate through each list item and log it
                foreach(int val in MyList)
                {
                    Debug.Log(val);
                }

                // let's do it again using a while loop, but this time backwards!
                int index = MyList.Count - 1;
                while (index >= 0)
                {
                    Debug.Log(MyList[index]);

                    --index;
                }
            }
        }

        // example of a method that returns something
        public string GetCoolDisplayString()
        {
            // we can use ? and : to write an if statement.
            // when comparing booleans we can also cut == true and == false and
            //  just implicitly infer the value
            return _myBool ? "Very Cool" : "Very Not Cool";
        }

        /// <summary>
        /// This is a documented member.
        /// This works on anything but with methods we can provide documentation for each param.
        /// This description will show up in intelisense when highlighting this method.
        /// </summary>
        /// <param name="coolNum>This describes what the coolNum parameter is for.</param>
        /// <return>
        /// This allows us to tell intelisense what this method will return.
        /// </returns>
        public int MyDocumentedMethod(int coolNum)
        {
            return coolNum * coolNum / coolNum - coolNum + coolNum;
        }

        // virtual methods allow us to declare methods that we can override in child classes
        public virtual void DoSomethingCool()
        {
            Debug.Log("This is cool right?");
        }
    }
}

Class Inheritence Example

Inheritence goes deeper then this example so if you really want to get into the nitty gritty of what you can do, I recommend reading Microsoft’s documentation.

namespace Example.CrashCourse
{
    // we use a colon to declare that this class is the child of another
    public class MyChildClass : MyClass
    {
        // remember DoSomethingCool from above? Well, let's override it
        // use the override keyword to do so
        public override void DoSomethingCool()
        {
            // this will call the method of the parent class
            //  so having this line here will log "This is cool right?" to the console.
            base.DoSomethingCool();

            // for that something cool, let's do something not cool and set our
            //  static CoolFeatureEnabled boolean to false
            MyClass.CoolFeatureEnabled = false;

            // all of the instance accessible variables are here too,
            //  so let's increment MyNumber while we're at it.
            ++MyNumber;
        }
    }
}

Events using delegates

Delegates allow us to subscribe methods to an event so our code can respond to something happening.

Declare the delegate

// we declare a delegate object like a method but without the body
public delegate void ObjectSpawned(Object theObject);

// we now want to delcare an object to store the delegate into
public ObjectSpawned OnObjectSpawned;

Our method to be called

public void ObjectWasSpawned(Object theObject)
{
    // do something with theObject here
}

Subscribe method to delegate and invoke the delegate

// we subscribe methods to a delegate using +=
OnObjectSpawned += ObjectWasSpawned;

// now we can call the delegate
if (OnObjectSpawned != null) OnObjectSpawned.Invoke();

Anonymous Methods

This is a concept that goes further then just delegates, but they can come in handy here.

So let’s say that you want to do something really small and writing a method would take more time. Or maybe you have some kind of local variable that you just want to work with right then and there? Say hellow to anonymous methods.

// () => is a lambda expression, which allows us to define methods without a name
OnObjectSpawned += (Object o) =>
{
    // do something with o here
};

BallisticNG Example

Alright so we’ve seen how to create and subscribe to events. So, how would this apply to BallisticNG?

Well, here’s an example of responding to ships spawning in a BallisticNG mod.

public class PrintShipNamesOnSpawn : ModRegister
{
    public override void OnRegistered()
    {
        BallisticEvents.Race.OnShipSpawned += OnShipSpawned;
    }

    private void OnShipSpawned(ShipRefs ship)
    {
        Debug.Log(ship.name);
    }
}