Alternative to ref as input-argument for delegate-functions

Greetings everyone,

I am currently working on my first game, and searching for a generic way to bind a function which increases/decreases a Character-Stat to a button-click:
For example my character looks like this:

public class Character {
    public bool m_rangedAttack = false;
    public float m_attackRangeInch = 8.0f;

    public float m_movementRange = 0.0f;
    public float m_wounds = 1.0f;

    public float m_numberUnits = 1.0f;

    public float m_hitChance = 1.0f;
    public float m_hitDamage = 0.0f;
    public float m_hitNumber = 0.0f;

    public float m_blockChance = 1.0f;
    public float m_blockDamage = 0.0f;
    public float m_blockNumber = 0.0f;

    public Character(){
    }
}

For the character editor I am creating a button (from a prefab) for each of these stats. Now I am searching for a generic way to bind a function to the on-click event. In best case i would like for each button to use the same function just with different captured variables. My current code for this looks like this:

public class CharaterCreatorScript : MonoBehaviour
{ 
    private Character m_char = new Character();
    
    private void addB(string name, ref GameObject prefab, ref GameObject canvas, ref float stats){
        GameObject button = (GameObject)Instantiate(prefab);
        GameObject plus         = button.transform.GetChild(1).gameObject;

        //[ToDo] Make this as generic as possible, ref is not possible due to anonymous function
        plus.GetComponent<Button> ().onClick.AddListener ( delegate {  increaseStat (ref s, 10.0f); });
    }

    private void Start() {
        GameObject pf_simpelButton = (GameObject)Resources.Load("Prefabs/SimpleButtonPrefab");

        GameObject anchor = GameObject.Find("ButtonListContent");

        addB("AttackRange",    ref pf_simpelButton, ref anchor, ref m_char.m_attackRangeInch);
        addB("MovementRange",  ref pf_simpelButton, ref anchor, ref m_char.m_movementRange);
        addB("Wounds",         ref pf_simpelButton, ref anchor, ref m_char.m_wounds);
        addB("NumberUnits",    ref pf_simpelButton, ref anchor, ref m_char.m_numberUnits);
        addB("HitChance",      ref pf_simpelButton, ref anchor, ref m_char.m_hitChance);
        addB("HitDamge",       ref pf_simpelButton, ref anchor, ref m_char.m_hitDamage);
        addB("HitNumber",      ref pf_simpelButton, ref anchor, ref m_char.m_hitNumber);
        addB("BlockChance",    ref pf_simpelButton, ref anchor, ref m_char.m_blockChance);
        addB("BlockDamge",     ref pf_simpelButton, ref anchor, ref m_char.m_blockDamage);
        addB("BlockNumber",    ref pf_simpelButton, ref anchor, ref m_char.m_blockNumber);
    }

    public void increaseStat(ref float stat, float max){
        if(stat < max){
            stat++;
            Debug.Log ("Increases to " + stat.ToString());
            updateText();
        }
    }
}

My current way of using a ref as input for the delegate is not possible in C#.
Is there a way to code this, or would i have to define a different function for each stat (e.g. increaseWounds, increaseRange,…), which would have access to my m_char member-variable?

Well, this is a complicated topic. In general you can not “store” a ref parameter anywhere except on the stack of the executing scope. ref and out parameters are actual pointers to memory and not managed references. That’s why they can’t be stored in a member variable. However you could create a wrapper class like the “VariableProxy” I’ve posted over here. It’s just a seperate getter and setter delegate which you have to provide yourself if you need read and write access. So you can use ordinary closures.

Though if this is an essential part of your game, it may make more sense to directly wrap each value in a simply wrapper class. That way you can actually pass around a managed object. If you have different types, you would need a seperate class (for example through a generic class). This should be much cleaner than tons of closures. Closures can imply quite a performance hit as they affect the general structure of your application in places you would not think of. Feel free to create some closures and view your compiled code in ILSpy to see what the compiler actually created for you.