Хорошо, ребята! Давайте поговорим о дженериках в C# простым и интересным способом!

Представьте, что у вас есть волшебная коробка, в которую можно положить любую игрушку. Называется «Игрушечный ящик». Обычно вам понадобится отдельная коробка для каждого типа игрушек — одна для машин, одна для кукол, одна для мячей и так далее. Но с волшебной «Коробкой для игрушек» у вас может быть только одна коробка, которая подходит для всех игрушек!

В C# дженерики работают аналогично. Это похоже на наличие специального ящика, называемого «Универсальный ящик», который может содержать данные любого типа — числа, слова, цвета, что угодно! Вместо создания нового блока (или метода) для каждого типа данных вы можете использовать этот «общий блок» для хранения и использования различных типов данных.

Вот как это выглядит в коде:

// This is our magical "Generic Box."
public class GenericBox<T>
{
    private T item;

    public void Put(T toy)
    {
        item = toy;
    }

    public T Get()
    {
        return item;
    }
}

Буква «T» в угловых скобках «‹T›» представляет тип данных, которые будут храниться в поле. Это может быть любой тип, который вы хотите: «T» для игрушек, «N» для цифр, «W» для слов или что-то еще, что вы можете придумать!

Теперь вы можете использовать эту волшебную «универсальную коробку» для хранения различных типов игрушек:

GenericBox<string> wordBox = new GenericBox<string>();
wordBox.Put("Hello, world!");
string word = wordBox.Get();
Debug.Log(word);

GenericBox<int> numberBox = new GenericBox<int>();
numberBox.Put(42);
int number = numberBox.Get();
Debug.Log(number);

GenericBox<bool> flagBox = new GenericBox<bool>();
flagBox.Put(true);
bool flag = flagBox.Get();
Debug.Log(flag);

Видите, как один и тот же «Универсальный ящик» используется для разных типов данных? Это как иметь одну волшебную коробку, которая адаптируется к любому типу игрушек, которые вы кладете внутрь!

Давайте углубимся в дженерики в C# на среднем уровне. Не волнуйся; Я по-прежнему сделаю это максимально простым для вас!

В Unity или C# вы можете использовать универсальные шаблоны для создания гибкого и многократно используемого кода, который работает с различными типами данных или компонентами. Таким образом, нам не нужно заново писать код для каждого объекта. Само моноповедение Unity использует дженерики, например. GetComponent<T>

Метод GetComponent<T> — это общий метод, предоставляемый классом Unity MonoBehaviour, который позволяет вам получать доступ к компонентам и работать с ними безопасным для типов способом. Это удобный и безопасный способ получить ссылку на компонент определенного типа без необходимости ручного приведения типов.

Давайте разберемся на примере:

Давайте создадим один класс Enemy и создадим два разных типа врагов, производных от этого класса. Затем мы будем использовать EnemyManager, чтобы найти этих врагов и справиться с ними.

  1. Враг :
using UnityEngine;

public class Enemy : MonoBehaviour
{
    public void Attack()
    {
        Debug.Log("Enemy attacks!");
        // Add your enemy attack logic here.
    }
}

2 . Два типа врагов (производные классы):

using UnityEngine;

public class EnemyTypeA : Enemy
{
    // Add any specific behavior for Enemy Type A here.
}

public class EnemyTypeB : Enemy
{
    // Add any specific behavior for Enemy Type B here.
}

3. Скрипт менеджера врагов:

using UnityEngine;

public class EnemyManager<T> : MonoBehaviour where T : Enemy
{
    private T[] enemies;

    private void Start()
    {
        // Find all enemy GameObjects of type T and store them in the enemies array.
        enemies = FindObjectsOfType<T>();
    }

    public void AttackAllEnemies()
    {
        foreach (T enemy in enemies)
        {
            enemy.Attack();
        }
    }
}

4. Скрипт GameManager:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    [SerializeField] private EnemyManager<EnemyTypeA> enemyTypeAManager;
    [SerializeField] private EnemyManager<EnemyTypeB> enemyTypeBManager;

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
        {
            enemyTypeAManager.AttackAllEnemies();
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            enemyTypeBManager.AttackAllEnemies();
        }
    }
}

Теперь, когда вы запускаете игру, вы можете создать в сцене два разных типа врагов. Прикрепите скрипт Enemy к обоим вражеским префабам, а затем создайте два префаба — EnemyTypeA и EnemyTypeB, каждый со своим уникальным внешним видом и поведением. Когда вы нажимаете «1» или «2» во время игры, все соответствующие типы врагов будут атаковать!

При таком подходе вы можете иметь столько типов врагов, сколько хотите, просто создавая новые классы, производные от класса Enemy. EnemyManager автоматически найдет и справится со всеми типами врагов, используя дженерики!

Дополнительные варианты использования дженериков:

Общие методы:

  1. Вы можете создавать универсальные методы, которые могут работать с различными типами данных или компонентов. Предположим, вы хотите, чтобы метод регистрировал имя компонента, прикрепленного к GameObject:
using UnityEngine;

public class GenericLogger : MonoBehaviour
{
    // Generic method to log the name of any component attached to a GameObject.
    private void LogComponentName<T>(T component) where T : Component
    {
        Debug.Log("Component Name: " + component.name);
    }

    private void Start()
    {
        // Usage example:
        Rigidbody rb = GetComponent<Rigidbody>();
        LogComponentName(rb); // This will log the name of the Rigidbody component.
    }
}

2. В нашем классе врагов у нас есть 2 типа врагов. Если я скажу, что хочу получить в сцене всех врагов типа 1, то мне придется написать функцию, которая добавит врага типа 1 в список. Позже я хочу добавить врага типа 2 в список, и снова мне нужно написать ту же функцию, чтобы добавить врага типа 2. Код повторяется. Решение состоит в том, чтобы написать общую функцию, которая принимает универсальный аргумент и добавляет ее в общий список. Одна и та же функция будет работать для всех типов врагов.

public List<T> GetEnemies<T>() where T is Enemy
{

  List<T> enemyList = new List<T>;

  foreach(Enemy enemy in scene)
  {
    if(enemy is T)
    {
      enemyList.Add(enemy as T);
    }

   }
   return enemyList;
 }

Обобщения помогают нам писать более гибкий и пригодный для повторного использования код, делая программирование намного проще и увлекательнее.

Надеюсь, вы поняли.