In this module, we will delve into advanced C# concepts that are essential for creating more complex and efficient Unity projects. Understanding these concepts will allow you to write more robust, maintainable, and performant code.

Key Concepts

  1. Delegates and Events
  2. LINQ (Language Integrated Query)
  3. Generics
  4. Reflection
  5. Attributes

  1. Delegates and Events

Delegates

A delegate is a type that represents references to methods with a particular parameter list and return type. Delegates are used to pass methods as arguments to other methods.

Example:

public delegate void MyDelegate(string message);

public class DelegateExample
{
    public void PrintMessage(string message)
    {
        Debug.Log(message);
    }

    public void Execute()
    {
        MyDelegate del = PrintMessage;
        del("Hello, Unity!");
    }
}

Events

Events are a way for a class to notify other classes or objects when something of interest occurs. Events are based on delegates.

Example:

public class EventExample
{
    public delegate void OnMessageReceived(string message);
    public event OnMessageReceived MessageReceived;

    public void SendMessage(string message)
    {
        if (MessageReceived != null)
        {
            MessageReceived(message);
        }
    }
}

public class Listener
{
    public void OnMessageReceived(string message)
    {
        Debug.Log("Message received: " + message);
    }

    public void Subscribe(EventExample eventExample)
    {
        eventExample.MessageReceived += OnMessageReceived;
    }
}

Exercise

  1. Create a delegate and an event in a Unity script.
  2. Create a listener class that subscribes to the event and handles the event when it is triggered.

Solution:

// EventExample.cs
public class EventExample : MonoBehaviour
{
    public delegate void OnMessageReceived(string message);
    public event OnMessageReceived MessageReceived;

    void Start()
    {
        Listener listener = new Listener();
        listener.Subscribe(this);
        SendMessage("Hello, Unity!");
    }

    public void SendMessage(string message)
    {
        if (MessageReceived != null)
        {
            MessageReceived(message);
        }
    }
}

// Listener.cs
public class Listener
{
    public void OnMessageReceived(string message)
    {
        Debug.Log("Message received: " + message);
    }

    public void Subscribe(EventExample eventExample)
    {
        eventExample.MessageReceived += OnMessageReceived;
    }
}

  1. LINQ (Language Integrated Query)

LINQ is a powerful feature in C# that allows you to query collections in a more readable and concise way.

Example:

using System.Linq;

public class LINQExample : MonoBehaviour
{
    void Start()
    {
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        var evenNumbers = numbers.Where(n => n % 2 == 0);

        foreach (var number in evenNumbers)
        {
            Debug.Log(number);
        }
    }
}

Exercise

  1. Create a list of strings and use LINQ to filter out strings that contain the letter 'a'.

Solution:

using System.Collections.Generic;
using System.Linq;

public class LINQExercise : MonoBehaviour
{
    void Start()
    {
        List<string> names = new List<string> { "Alice", "Bob", "Charlie", "David" };
        var namesWithA = names.Where(name => name.Contains('a'));

        foreach (var name in namesWithA)
        {
            Debug.Log(name);
        }
    }
}

  1. Generics

Generics allow you to define classes, methods, and data structures with a placeholder for the type of data they store or use.

Example:

public class GenericExample<T>
{
    private T item;

    public void SetItem(T newItem)
    {
        item = newItem;
    }

    public T GetItem()
    {
        return item;
    }
}

public class TestGeneric : MonoBehaviour
{
    void Start()
    {
        GenericExample<int> intExample = new GenericExample<int>();
        intExample.SetItem(42);
        Debug.Log(intExample.GetItem());

        GenericExample<string> stringExample = new GenericExample<string>();
        stringExample.SetItem("Hello, Unity!");
        Debug.Log(stringExample.GetItem());
    }
}

Exercise

  1. Create a generic class that can store and retrieve any type of data.

Solution:

public class GenericStorage<T>
{
    private T data;

    public void StoreData(T newData)
    {
        data = newData;
    }

    public T RetrieveData()
    {
        return data;
    }
}

public class TestGenericStorage : MonoBehaviour
{
    void Start()
    {
        GenericStorage<float> floatStorage = new GenericStorage<float>();
        floatStorage.StoreData(3.14f);
        Debug.Log(floatStorage.RetrieveData());

        GenericStorage<bool> boolStorage = new GenericStorage<bool>();
        boolStorage.StoreData(true);
        Debug.Log(boolStorage.RetrieveData());
    }
}

  1. Reflection

Reflection allows you to inspect and interact with the metadata of your code at runtime. This can be useful for creating more dynamic and flexible applications.

Example:

using System;
using System.Reflection;

public class ReflectionExample : MonoBehaviour
{
    void Start()
    {
        Type type = typeof(Transform);
        MethodInfo method = type.GetMethod("Translate", new Type[] { typeof(Vector3) });

        if (method != null)
        {
            Debug.Log("Method found: " + method.Name);
        }
    }
}

Exercise

  1. Use reflection to get and set the value of a private field in a class.

Solution:

using System;
using System.Reflection;

public class ReflectionExercise : MonoBehaviour
{
    private string secretMessage = "This is a secret!";

    void Start()
    {
        Type type = typeof(ReflectionExercise);
        FieldInfo field = type.GetField("secretMessage", BindingFlags.NonPublic | BindingFlags.Instance);

        if (field != null)
        {
            Debug.Log("Original value: " + field.GetValue(this));
            field.SetValue(this, "New secret message!");
            Debug.Log("Updated value: " + field.GetValue(this));
        }
    }
}

  1. Attributes

Attributes provide a powerful method of associating metadata or declarative information with code. They can be used to add additional information to classes, methods, properties, and more.

Example:

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyCustomAttribute : Attribute
{
    public string Description { get; }

    public MyCustomAttribute(string description)
    {
        Description = description;
    }
}

[MyCustomAttribute("This is a custom attribute for the class.")]
public class AttributeExample : MonoBehaviour
{
    [MyCustomAttribute("This is a custom attribute for the method.")]
    void Start()
    {
        Debug.Log("AttributeExample started.");
    }
}

Exercise

  1. Create a custom attribute and apply it to a class and a method.

Solution:

using System;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class InfoAttribute : Attribute
{
    public string Info { get; }

    public InfoAttribute(string info)
    {
        Info = info;
    }
}

[Info("This class demonstrates the use of custom attributes.")]
public class CustomAttributeExample : MonoBehaviour
{
    [Info("This method is marked with a custom attribute.")]
    void Start()
    {
        Debug.Log("CustomAttributeExample started.");
    }
}

Conclusion

In this module, we covered several advanced C# concepts that are crucial for developing sophisticated Unity projects. We explored delegates and events, LINQ, generics, reflection, and attributes. By mastering these concepts, you will be able to write more efficient, flexible, and maintainable code in your Unity projects.

Next, we will move on to Coroutines and Asynchronous Programming, where we will learn how to handle asynchronous operations in Unity.

© Copyright 2024. All rights reserved