Skip to content

C sharp

  • C# is open-source. If you ever want to find the code behind a particular class, go to a page like this and click the “Source” link toward the top:
    • Pasted image 20240520141654.png
  • null!: the “null-forgiving” operator (reference). Use it when you know that an expression can’t be null but the compiler can’t tell. For example, in Godot, you can set some variables through the game engine itself, but C# won’t know that they’re set, so you would initialize the variable using null!:
    [Export]
    private Sprite2D _bullet = null!;
    • Note that structs and classes have different ways of becoming nullable. Instances of a class can always be null, but instances of a struct need to be wrapped in Nullable<T>, which is what happens when you make a FooStruct? myStruct. When wrapping in a Nullable, you should use HasValue and Value and not just != null and myStruct!.
  • The equivalent of Java’s IllegalStateException is System.InvalidOperationException (reference).
  • Use a Stopwatch to keep track of how much time has elapsed:
    Stopwatch _stopWatch = new();
    _stopWatch.Start();
    // do something
    _stopWatch.Stop();
    TimeSpan ts = _stopWatch.Elapsed;
    string elapsedTime = string.Format("{0:00}:{1:00}.{2:00}", ts.Minutes, ts.Seconds, ts.Milliseconds / 10);

You actually just do this from the command line using the dotnet tool (reference):

  • dotnet new sln -o MyProject
  • dotnet new console -o MyProject.Main
  • dotnet sln MyProject.sln add MyProject.Main/MyProject.Main.csproj
  • dotnet build
  • dotnet run
    • It’ll show “Hello, World!”

See C sharp and JSON.

At least in VSCode, if you try to evaluate an expression like foo.ToList() in a file that didn’t explicitly write using System.Linq;, you’ll get an error like this:

foo.Values.ToList()
error CS1061: 'Dictionary<int, int>.ValueCollection' does not contain a definition for 'ToList' and no accessible extension method 'ToList' accepting a first argument of type 'Dictionary<int, int>.ValueCollection' could be found (are you missing a using directive or an assembly reference?)

You can still use it via System.Linq.Enumerable.ToList, which is a bit unwieldy: System.Linq.Enumerable.ToList(foo.Values)

If you have a generic class and you want to get the name of that class with a specific type in it, you can’t just do nameof(GenericClass) or else you get something like GenericClass`1. Instead, you have to do this: typeof(GenericClass<SomeTypeHere>).Name.

If you need an arbitrary bunch of parameters (e.g. a Pair of something), try Tuple:

  • Anonymous: Tuple<X, Y>
  • Named: (X x, Y y)

The named variant is really nice for saying what something represents, that way you don’t just have Tuple<int, int> and wonder which int is what:

public (int cost, int distance) GetTripDetails() {
return new(5, 27);
}
public void ShowHowToCall() {
(int cost, int distance) = GetTripDetails();
// ...
}

This applies even to iterating over dictionaries:

// Both of these are equivalent
// KeyValuePair
foreach (KeyValuePair<VolumeSliderId, float> kvp in localSaveData.VolumeSliderValues)
{
LocalSettings.SetVolumeValue(kvp.Key, kvp.Value, false);
}
// Tuple - notice the names for the key and value, which makes it clearer IMO
foreach ((VolumeSliderId volumeSliderId, float value) in localSaveData.VolumeSliderValues)
{
LocalSettings.SetVolumeValue(volumeSliderId, value, false);
}

Simple example:

public class TargetLocationComponent()
{
public event EventHandler<EventArgs> ReachedTarget = null!;
// Events can only be emitted from the owning class
public void EmitReachedTarget()
{
ReachedTarget(this, EventArgs.Empty);
}
}
// From some other code, raise the event:
tlc.EmitReachedTarget();
// From some listener code, subscribe to the event:
TargetLocationComponent tlc = new();
tlc.ReachedTarget += (object sender, EventArgs args) => /* do something */;

Here’s an example:

// Method signature:
public static T AddComponent<T>(Node entity, T component)
where T : Component
{/*...*/}
// Calling that method:
MethodInfo? addComponentGenericMethod =
typeof(Ecs).GetMethod("AddComponent")
?? throw new InvalidOperationException("AddComponent method not found");
MethodInfo addComponentSpecificMethod = addComponentGenericMethod.MakeGenericMethod(c.GetType());
addComponentSpecificMethod.Invoke(null, [entity, c]);

Note that this may not work on all platforms (for example, maybe iOS) since it requires JIT compilation.

Consider this code:

public enum Fruit
{
Apple,
Banana,
}
public static void Main()
{
Fruit? fruit = (new List<Fruit>()).FirstOrDefault();
Console.WriteLine(fruit); // this will always print Apple (i.e. "fruit.HasValue" will always be true)
}

The issue is that the default for an enumeration is the 0 value of that enumeration, which in this case is Apple (but if that weren’t defined, it would literally be the value 0). Your options are:

  • Manifest None = 0 in the Fruit enum
  • Change Apple to be Apple = 1 and explicitly check for fruit == 0
  • Write your own version of Find or FirstOrDefault (probably as an extension).
  • Change new List<Fruit>()new List<Fruit?>()
    • (this probably isn’t a great option because you’d realistically have to change practically every type in the call stack to get to whichever function you’re writing)

Anonymous lambdas capturing the wrong value

Section titled Anonymous lambdas capturing the wrong value

(kw: closure)

Example program:

public class Program
{
public static void Main()
{
List<Action> functions = [];
for (int i = 0; i < 10; i++) {
functions.Add(() => Print(i));
}
for (int i = 0; i < 10; i++) {
functions[i]();
}
}
public static void Print(int i) {
Console.WriteLine(i);
}
}

The output of this program is 10 10 10 10 ... and not 0 1 2 3 4 5 .... To fix this, simply copy i into a local variable and use that:

for (int i = 0; i < 10; i++) {
int localI = i;
functions.Add(() => Print(localI));
}