C#: Generics

Hey I’m attempting to make a class that can animate different types of Phases.
Those phases could return different things: floats, objects, whatever…

The code speaks for itself but I can’t compile it, how am I supposed to achieve this?

using System;
using UnityEngine;


[Serializable]
public abstract class Phase<T>
{
    public float ExecutionTime = 1f;
    public abstract T Run(float _executeTime);
    public abstract void Init();
}

public class Phase_Float : Phase<float>
{
    public float Start;
    public float End;
    public EasingFunctions.Functions Function;
    private EasingFunctions.EasingFunc Func;

    public override void Init()
    {
        Func = EasingFunctions.GetEasingFunction(Function);
    }
 
    public override float Run(float _executeTime)
    {
        return Func(Start, End, _executeTime / ExecutionTime);
    }
}

public class PhasePlayer<T> where T : Phase<T>
{
    // runs the phases...
}

public class Impl : MonoBehaviour
{
// TODO: THIS IS NOT ALLOWED
    public Phaser<Phase_Float> MyPhaser = new Phaser<Phase_Float>();
}

This is the error in the IDE:

Given your declaration here:

public class PhasePlayer<T> where T : Phase<T>

Then T would have to be something like Phase_Float<Phase_Float>

So maybe you want:something like:
public class PhasePlayer<P, T> where P : Phase<T>?
Or
public class PhasePlayer<T> where T : Phase?
That second one would probably require a non-generic super-class of Phase

2 Likes

Another case where the point of generics has been misunderstood. I recently posted a rather lengthy explanation on Unity Answers. In short: generic parameters essentially does the opposite of inheritance. It actively seperates classes with different type arguments from each other. The point of OOP and inheritance is to provide different logic through a common / abstract interface so the individual objects can be treated equally / in an abstract way. Generics provide a single logic in an abstract manner so it works with different types. However the concrete types are completely incompatible. They just share the same code / logic but do not have a common / compatible interface. Just like a List<int> and a List<string> are completely incompatible to each other. There is no common base class. The two types are completely seperate. All they have in common is the same logic / code behind them.

So yes, I don’t think generics make much sense here. You haven’t really provided any example how you plan to actually use this construct.

@Bunny83 I got what I needed from PraetorBlue’s answer.

You can see how I use it below.
I don’t think what I’m doing is so fundamentally wrong though, it seems useful to me :smile:

The Phase class and its variations :

using System;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;

[Serializable]
public abstract class Phase<T>
{
    public void Reset()
    {
        ElapsedTime = 0f;
    }

    public float ElapsedTime { get; private set; }
    public float ExecuteTime = 1f;
    public float RemainingTime => ExecuteTime - ElapsedTime;
    public float CompletionPercent => Mathf.Clamp01(ElapsedTime/ ExecuteTime);

    public bool HasFinishedRun => ElapsedTime >= ExecuteTime;

    public T DoRun(float _currentTime)
    {
        ElapsedTime = _currentTime;
        return Run();
    }
   
    protected abstract T Run();

    public void MasterEnter()
    {
        // Debug.Log("Enter");
        ElapsedTime = 0f;
        Enter();
    }
    public void MasterExit()
    {
        Exit();
    }
   
    protected virtual void Enter()
    {
    }

    protected virtual void Exit()
    {
        // Debug.Log("Exit");
    }
   
    public virtual void Init()
    {
       
    }
}

[Serializable]
public class Phase_Bool : Phase<bool>
{
    private bool Consumed;

    protected override void Enter()
    {
        Consumed = false;
    }

    protected override bool Run()
    {
        var saved = !Consumed;
       
        if (Consumed == false)
        {
            Consumed = true;
        }

        return saved;
    }
}


[Serializable]
public class Phase_Float : Phase<float>
{
    [HorizontalGroup("A")] public float Start;
    [HorizontalGroup("A")] public float End;
    public EasingFunctions.Functions Function;
    private EasingFunctions.EasingFunc Func;

    public override void Init()
    {
        Func = EasingFunctions.GetEasingFunction(Function);
    }
   
    protected override float Run()
    {
        return Func(Start, End, ElapsedTime / ExecuteTime);
    }
}

The phaser (the class that plays each phases - I create them in the inspector)

// 8:13 PMFeast

using System;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class Phaser<P, T> where P : Phase<T>
{
    public float Delay = 0f;
    public PlayMode Mode = PlayMode.Once;
    public int CurrentPhaseIndex { get; private set; } = 0;
    public List<P> MyPhases = new List<P>();
    public float TotalTime { get; private set; }
    public float PhaseTime { get; private set; }
    public P CurrentPhase => MyPhases[CurrentPhaseIndex];
    private bool IsInit;

    public Phaser()
    {
        Init();
    }
   
    public enum PlayMode
    {
        Once = 0,
        Loop = 1,
    }
   
    public void Init()
    {
        Reset();

        if (IsInit == false)
        {
            IsInit = true;
            foreach (var p in MyPhases)
            {
                p.Init();
            }
        }
    }
    public void Reset()
    {
        IsFirstRun = true;
        TotalTime = 0f;
        PhaseTime = 0f;
        CurrentPhaseIndex = 0;
        foreach (var p in MyPhases)
        {
            p.Reset();
        }
    }

    private bool IsFirstRun = true;
    /// <summary>
    /// Returns isRunning.
    /// </summary>
    public bool Run(out T _result)
    {
        _result = default;
       
        var phaseCount = MyPhases.Count;

        switch (Mode)
        {
            case PlayMode.Once:
                if (CurrentPhaseIndex >= phaseCount)
                {
                    return false;
                }
                break;
            case PlayMode.Loop:
                CurrentPhaseIndex %= phaseCount;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        if (IsInit == false || phaseCount == 0)
        {
            return false;
        }

        if (Delay > 0f && TotalTime <= Delay)
        {
            TotalTime += Time.deltaTime;
            return true; // we're playing... just delayed
        }
       
        // When the first phase starts we should call it.
        if (CurrentPhaseIndex == 0 && IsFirstRun)
        {
            IsFirstRun = false;
            CurrentPhase.MasterEnter();
        }
       
        TotalTime += Time.deltaTime;
        PhaseTime += Time.deltaTime;
           
        if (CurrentPhase.HasFinishedRun)
        {
            CurrentPhase.MasterExit();

            PhaseTime -= CurrentPhase.ExecuteTime;
           
            CurrentPhaseIndex += 1;
            // Debug.Log($"CurrentPhaseIndex: {CurrentPhaseIndex}");
           
            if (CurrentPhaseIndex >= phaseCount && Mode != PlayMode.Loop)
            {
                return false;
            }
           
            if (Mode == PlayMode.Loop)
            {
                CurrentPhaseIndex %= phaseCount;
            }

            OnNewPhase();
           
            CurrentPhase.MasterEnter();
        }

        _result = CurrentPhase.DoRun(PhaseTime);
       
        return true;
    }

    public void OnNewPhase()
    {
       
    }
}

Here’s how I use it, in this case it’s to create a prefab every x seconds depending on the time I set in the inspector.

// 9:12 PMFeast

using System;
using UnityEngine;

public class Impl : MonoBehaviour
{
    public Phaser<Phase_Bool, bool> Phaser = new Phaser<Phase_Bool, bool>();

    public GameObject Prefab;
    public Transform Target;

    public bool DoUpdate;
   
    private void Awake()
    {
        Phaser.Init();
    }

    void Update()
    {
        if (!DoUpdate) return;
       
        var playing = Phaser.Run(out var phaseResult);
        if (playing)
        {
            if (phaseResult)
                Instantiate(Prefab, Target.position, Quaternion.identity);
            Debug.Log($"myFloat: {phaseResult} @ {Phaser.PhaseTime}    ------- Total time: {Phaser.TotalTime}");
        }
        else
        {
            Debug.LogError($"myFloat: {phaseResult} @ {Phaser.PhaseTime}    ------- Total time: {Phaser.TotalTime}");
        }
    }
}

Another use case, animating the scale of a Transform:

public class Impl2 : MonoBehaviour
{
    public Phaser<Phase_Float, float> Phaser = new Phaser<Phase_Float, float>();

    public Transform Target;

    private void Awake()
    {
        Phaser.Init();
    }

    void Update()
    {
        var playing = Phaser.Run(out var phaseResult);
        if (playing)
        {
            Target.localScale = Vector3.one * phaseResult;
        }
        else
        {
        }
    }
}

Bunny83 is right for the most part, but there is a way to make generics compatible to each another, and this can make sense in some cases, if they share a common interface.

But regardless, a general rule of thumb is to NOT use generics if your aim is to save yourself from typing (i.e. repetition). I have burned myself many times with this while I was learning the ropes, it simply doesn’t work like that.

However, I’ve seen what you did, and it seems that you simply wanted to change certain implementation based on a type. Even if this generic solution works, I wouldn’t recommend it, it’s super smelly. And I would be super pissed for having to decipher this kind of code in a professional environment.

Normally you do this either through inheritance, or if you have issues because of incompatible method signatures (don’t forget about overloads though), you simply expose inner parts in such a way so that it accepts a custom consumer lambda.