Long-term use of ref/out?

-question, in short, at the bottom-

I’m using C#, trying to implement achievements (which are tied directly into unlockables) into my mobile game. The problem is, I’m trying to store a reference to a boolean value for later use, and I’m starting to realize I’ve never had to do this before and I’m not sure how to make it work.

The way I’ve tried to set it up, I created a public class called Achiev, which contains all the relevant data. That’s the achievement ID, name, boolean to determine if it’s unlocked, a few other things, and finally, a reference to the boolean that determines if the unlockable is available to use in the game.

public class Achiev //class for achievements
		public int number; //amount of whatever thing you need to unlock this (if applicable)
		public string achID; //Achievement ID - Google Play
		public string name;
		public string description;
		public string itemType; //"pixel block style", "bubble background", etc
		public int level; //"rank" of tiered achievements. Can be 1, 2, or 3. Each one has its own overlaid sub-icon
		public Sprite icon;
		public bool unlocked; //has this achievement been unlocked?
		public bool unlockRef; //reference(?) to bool variable which actually determines if unlockable is available to player

		public Achiev(int num, string ID, string nam, string desc, string type, int lev, Sprite ico, bool unl, ref bool uRef)
			number = num;
			achID = ID;
			name = nam;
			description = desc;
			itemType = type;
			level = lev;
			icon = ico;
			unlocked = unl;
			unlockRef = uRef;

Unlockables are all tied to boolean arrays - one 1-dimensional array for each type of unlockable. What I tried to do is have a ref boolean value on each instance of the Achiev class point to a bool in one of those various arrays, so when it comes time to unlock that achievement in the code, it can simply set that ref value to true and that thing will be shown as unlocked. It’s not necessary, honestly, but it does put all that info together in one place in the code so it’s easy to change.

Here’s an example of one of the achievements in code:

achievementList <em>= new Achiev(250000,"CgkInv_rlsMXEAIQDA","Adept Number Wizard","Earn a total of 250,000 points.","Blue blocks background",1,cst.sprNumberWiz,unlockAchievements*,ref unlockBackground[1]);*</em>

It should be obvious why, because the variable declaration in the class itself isn’t a reference (as far as I can tell, syntax won’t let me do that), but when I later go on to change the value of unlockRef, it certainly doesn’t reflect that change in the referenced variable. It seems ref and out keywords are only meant to work within the limited scope of a single function. Quick research says pointers might do the trick, but they’re considered ‘unsafe’ code, so I would assume it would be better to go with plan B (hard-coding the boolean values into the function which unlocks variables) instead.
So, I’m not so much asking this question because I’m stuck and need help, but knowing a better way to handle this kind of thing will definitely help in the future when I’m coding more expansive and complex games, and it would help now so I can do this right the first time.
Is there a way to store a reference to another variable like this for later use, or simply a better way to handle what I’m trying to do here?
Thanks for your time~

The short answer is No, there isn’t a way to store a reference / pointer to a variable without an unsafe context.

“ref” and “out” are actually implemented as pointers, however due to the compiler restrictions it’s ensured that no unsafe situation can occur. In a safe context you can’t store pointers in a variable. The only “pointer like” type is a reference which only works for reference types.

If you want “reference access” to a valuetype variable you can use a wrapper class. This class could use either closure get / set methods which you setup accordingly for your variable, or use relfection.

Here’s an example wrapper with the closure approach:

public class ValueTypeWrapper<T>
    private System.Func<T> getMethod;
    private System.Action<T> setMethod;
    public ValueTypeWrapper(System.Func<T> aGetMethod, System.Action<T> aSetMethod)
        getMethod = aGetMethod;
        setMethod = aSetMethod;
    public T Value
        get{ return getMethod();}
        set{ setMethod(value); }

With this class you can create an instance of that class to get / set for example a boolean variable of another class:

public class TargetClass
    public bool targetVar;

public class SomeOtherClass : MonoBehaviour
    ValueTypeWrapper<bool> boolRef;
    void Start()
        TargetClass target = GetComponent<TargetClass>();
        boolRef = new ValueTypeWrapper<bool>( ()=>target.targetVar, v=>targetVar=v);

Since “boolRef” is a class you can pass it’s reference around and store it somewhere if you like. You can use the “Value” property to read / write the bool variable in the target class.

// this will turn the "targetVar" in the target class instance to "true".
boolRef.Value = true;

A reflection based approach allows a more dynamical approach but it’s way slower and might be even more difficult to use (and it’s easier to introduce runtime errors if you’re not careful).

However even a wrapper class (kind of adapter pattern) might be useful in some cases, it’s usually better to use references to the class itself and just read / write the variables you need directly. You can abstract this a little bit with interfaces.

public interface IGroundable
    bool IsGrounded {get;}

public class TargetClass : MonoBehaviour, IGroundable
    private bool m_IsGrounded;
    public bool IsGrounded {get {return m_IsGrounded; }}

That way you can assign a reference to an instance of TargetClass to a “IGroundable” reference and use the “IsGrounded” property to check the state without knowing the actual type.

Directly accessing / referencing individual members of a class actually isn’t possible without pointers. Such an approach also kind of “breaks out” of the OOP nature where you usually work with objects and accessing it’s members and don’t deal with the members seperately.