Is it possible to call a generic method? 'Better than how I am now'

I am attaching classes to my players with methods that do logic like so:

public class Attack()
{
    public void PlayerAttack()
        {
               //logic
        }
  
    public class Defend()
    {
        public void PlayerDefend()
            {
                   //logic
            }
        }
     }

public class Magic
{
    public PlayerFireBall()
        {
            //logic
        }
    }
         //Etc

I know this does not look good but I have a system.

I was wondering how I could go about Creating a generic system that will be able to call methods easily.
Currently I have each method name being put into a list and I populate a UI with the names. That way I can look up by name what the user tries to call, but I know there is a better way.

Possibly a dictonary containing the method? (But I don’t like this, because I have no idea how to actually call the meathod from inside a dictonary):

    public Dictionary<string, System.Type> components = new Dictionary<string, System.Type>();
    components.Add("PlayerAttack", typeof(PlayerAttack));

Is there a way to do this better?
Please let me know if you have any advice.
Thanks!

This is more a Software Engineering Answer and less a Unity Answer.

Looking at what you have for your class names I can only guess that you not after Abstract Objects but Abstract Behavior. instead of trying to use Generics for this, try and use interfaces, this is likely what your after.

What you do is you define an interface which will be describing what the implementing classes will be doing, not how as that will be determined on a per class basis. Interfaces can only have Methods and properties associated with them so no variables or data. and typically the standard convention of an interface’s name is to prefix with an “I” and postfix an “-able” or “-ible”

    public interface IAttackable
    {
      void Attack();
    }
    public interface IDefendable
    {
      void Defend();
    }
    public interface ICastable
    {
      void Cast();
    }

then you have your player class simply implement this interface.

As you can see below the Player Class is inheriting from MonoBehavior, but is now also implementing the IAttackable,IDefendable, and ICastable interfaces,Now other classes can see the player class and immediately they’ll know that Players can attack, defend, or cast.

    public class Player : Monobehavior, IAttackable, IDefendable, ICastable
        {
            public void Attack(){}
            public void Defend(){}
            public void Cast(){}
        }

or if the player can cast multiple spells… you can instead have the player store a List of ICastible

    public class Player : Monobehavior, IAttackable, IDefendable
    {
        public List<ICastible> Spells;

        public void Attack(){}
        public void Defend(){}

        public void CastSpell(int index)
        {
              if(Spells.Count==0)
                   return;

              // quick way for avoiding out-of-bounds exceptions
              index = index%Spells.count;
              
              Spells[index].Cast();
              
        }
    }

the benefit with Interfaces is that now you don’t care what the implementing class actually is, just that it must be able to cast a spell to be allowed in the list. so you can have wildly different types of classes in that List (healing spells, fire spells, debuffing spells, or even special abilities which are not spells per se but can be casted) and the List only focuses on the one thing that actually cares about… the Cast() function. The Player Class doesn’t need to worry how a healing spell works, just that it can cast it. this in turn makes everybody’s life easier (the programmers and the classes itself)

And there you have it, your “generic” system that’s capable of calling methods easily.