No history yet

Advanced C# Concepts

Writing Smarter Code

You've got the basics of C# down. You can create classes, write methods, and manage variables. Now it's time to level up. We're going to explore a few advanced concepts that are crucial for building complex, high-performance games in Unity. These tools help you write code that is not just functional, but also clean, efficient, and easier to manage as your projects grow.

Mastering these concepts will help you write code that is more flexible and efficient.

Asynchronous Programming

Imagine your game needs to load a large file from the internet, like a player's profile or new level data. If you write the code in a simple, straightforward way, the entire game will freeze while it waits for the download to complete. The screen will lock up, animations will stop, and the player will think the game crashed. This is called synchronous execution. One task must finish completely before the next one can start.

Asynchronous programming solves this. It allows you to start a long-running task, like a download, and then let your game continue running. When the task is finally finished, it notifies your code so you can handle the result. This keeps your game responsive and smooth.

In C#, this is typically handled with the async and await keywords. You mark a method with async to signal that it can perform operations without blocking the main thread. Inside that method, you use await to pause the method's execution until a specific asynchronous task is complete.

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class DataDownloader : MonoBehaviour
{
    // Mark the method as async
    public async Task DownloadDataAsync(string url)
    {
        Debug.Log("Starting download...");

        using (UnityWebRequest request = UnityWebRequest.Get(url))
        {
            // await tells the program to wait here without freezing the game
            await request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                Debug.Log("Download complete! Data: " + request.downloadHandler.text);
            }
            else
            {
                Debug.Log("Error: " + request.error);
            }
        }
    }
}

While this code is running, the rest of your game—player movement, animations, UI—keeps going. The game's frame rate remains stable, providing a much better user experience.

Delegates and Events

Delegates are essentially type-safe function pointers. That sounds complicated, but the idea is simple. A delegate is a variable that holds a reference to a method. You can then call the method through the delegate variable. Think of it like a remote control for a method. The key is that any method assigned to a delegate must have a specific signature (return type and parameters) defined by the delegate.

Events are a mechanism that builds on delegates. They allow a class to notify other classes when something interesting happens. The class that sends the notification (the publisher) doesn't need to know anything about the classes that receive it (the subscribers). This creates a clean separation of concerns.

This pattern is invaluable in game development. When the player's health drops to zero, the player object shouldn't be responsible for updating the UI, playing a sound effect, and triggering a game over screen. It should just announce, "I've died!"

Here's how that works in practice. The Player class would define an event. Other systems, like a UIManager or AudioManager, can subscribe their own methods to that event.

using System;
using UnityEngine;

public class Player : MonoBehaviour
{
    // 1. Define a delegate for the event
    public delegate void PlayerDiedAction();
    // 2. Define the event based on the delegate
    public static event PlayerDiedAction OnPlayerDied;

    public int health = 100;

    public void TakeDamage(int damage)
    {
        health -= damage;
        if (health <= 0)
        {
            // 3. Fire the event if it has subscribers
            OnPlayerDied?.Invoke();
        }
    }
}

// Another script, for example UIManager.cs
public class UIManager : MonoBehaviour
{
    void OnEnable()
    {
        // Subscribe to the event
        Player.OnPlayerDied += ShowGameOverScreen;
    }

    void OnDisable()
    {
        // Unsubscribe to prevent errors
        Player.OnPlayerDied -= ShowGameOverScreen;
    }

    void ShowGameOverScreen()
    {
        Debug.Log("GAME OVER");
        // Logic to show UI panel goes here
    }
}

When the player's health drops to zero, it calls OnPlayerDied?.Invoke(). The ? is a null-check to make sure at least one object has subscribed. The UIManager hears the event and runs its ShowGameOverScreen method. The Player script has no direct reference to the UIManager, making your code much more modular and easy to manage.

LINQ

LINQ stands for Language-Integrated Query. It's a powerful feature in C# that allows you to query data from collections (like lists or arrays) using a syntax that's clean and easy to read. It saves you from writing complex for loops and if statements for common data-filtering tasks.

Suppose you have a list of all enemies in a scene and you want to find all of them that are within 10 meters of the player and have less than 50 health. Without LINQ, you'd have to loop through every enemy and check the conditions manually.

// The old way, without LINQ
List<Enemy> nearbyWeakEnemies = new List<Enemy>();
foreach (Enemy enemy in allEnemies)
{
    float distance = Vector3.Distance(player.transform.position, enemy.transform.position);
    if (distance < 10.0f && enemy.Health < 50)
    {
        nearbyWeakEnemies.Add(enemy);
    }
}

With LINQ, you can express this much more elegantly.

using System.Linq;

// ...

// The new way, with LINQ
var nearbyWeakEnemies = allEnemies
    .Where(enemy => Vector3.Distance(player.transform.position, enemy.transform.position) < 10.0f)
    .Where(enemy => enemy.Health < 50)
    .ToList();

LINQ provides many other useful methods, like OrderBy to sort results, Select to transform them, and FirstOrDefault to find the first item that matches a condition. It can make your data manipulation code dramatically simpler and more readable.

Quiz Questions 1/5

Your Unity game freezes for several seconds when downloading a new level file, making the UI unresponsive. Which C# feature is specifically designed to handle this kind of long-running operation without blocking the main game loop?

Quiz Questions 2/5

In C#, what is the best description of a delegate?

These concepts—asynchronous programming, delegates, events, and LINQ—are powerful tools. Using them effectively will help you write more professional, scalable, and maintainable Unity projects.