- Home /
How can I keep a member var that refrences a public var in another class?
Hey there, I was trained in C++ primarily for game dev but for the last two years have been using unity and C#.
I've pretty much had to teach myself the differences and what not and now I'm trying to do something that I can't seem to figure out the C# "way" to do it....
Here is some quick pseudo-code of what I'm trying to accomplish... Thanks!
public class MyClass
{
public bool bBool;
private float* pData;
void Start()
{
if (bBool)
pData = OtherClass.fData;
else
pData = DifferntClass.fData;
}
public void OtherFunction(float _fNewData)
{
(*pData) = _fNewData;
}
}
Answer by Owen-Reynolds · Oct 24, 2013 at 12:21 AM
Can't do it with floats. The whole C#/Java idea specifically forbids keeping a pointer to a primitive data type. You can only have (implicit) pointers to "managed" data types.
New'd classes are "managed" by the framework, meaning there's an invisible smart pointer which tracks them during garbage collection steps, and, ummm, makes sure they are "safe." So you have to wrap pointed-to vars in a class. Once you have that, you can no problem say something like F.pData=12;
Of course, since all Unity monobehavior scripts are classes, in practice you keep a pointer to the script, and scriptRef-dot-varName.
--- more ---
Part of the trick is that all C# variables of type class are really pointers. C# encourages you to think of them as just regular vars -- you NEW them yourself and always use var-dot-field. But for any class var, you always have the option to just write A=B, and you're really reassigning the pointer.
In your mind, when you declare someClass A;
, you decide whether you intend to use it as a regular variable, or as a pointer.
Standard wrapper code might be like:
class playerScript : Monobehavior { // regular Unity script
// public float cow;
// I want anyone to keep a pointer to this, so rewrite as float in class
// Cow wrapper, could have been defined anywhere,
public class floatHolder {
public int val;
}
public floatHolder myCow, myDog;
// will point to someone else's cow,dog, so I won't New it:
floatHolder otherCow;
void Awake() {
// cow=6; // what I wanted to do
// but cow is now wrapped in a class, so:
myCow = new Cow();
myCow.val = 6;
// same with my dog
}
void Start() {
// runs after Awake, so I know Steve's cow exists:
othercow = GameObject.Find("steve").GetComponent<playerScript>().myCow;
otherCow.val++; // added 1 using ref to Steve's cow
// now reassign pointer to his dog, for no reason:
otherCow = GameObject.Find("steve").GetComponent<playerScript>().myDog;
// can even point to one of my own:
otherCow = myDog;
}
I think from what I've gathered from your other reply and a lil further reading is...
I need my values in the other classes (OtherClass and DifferntClass) to share a common class type that can be referenced, although I'm still not sure on the exact code... I'm about to try something like this...
public class CommonData
{
float fData;
int nData;
...
...
}
public class OtherClass : $$anonymous$$onoB
{
CommonData myData;
}
public class DifferntClass : $$anonymous$$onoB
{
CommonData myData;
}
public class $$anonymous$$yClass : $$anonymous$$onoB
{
public bool bBool;
private CommonData pData;
void Start()
{
if (bBool)
pData = OtherClass.$$anonymous$$yData;
else
pData = DifferntClass.$$anonymous$$yData;
}
public void OtherFunction(float _fNewData)
{
pData.fData = _fNewData;
}
}
Just after I expanded my reply, I realized this is correct (and better than $$anonymous$$e.) CommonData as a class allows you to point to it.
Actually, C# allows you to pass any variable, be it value or reference type by reference to subroutines (just like pointers). You just have to use the ref keyword in declaring the parameters.
This script demonstrates my point:
using UnityEngine;
using System.Collections.Generic;
public class script : $$anonymous$$onoBehaviour {
public int i;
public List<int> intList = new List<int>();
void Start(){
i = 0;
ChangeInt(i);
Debug.Log("Int without ref: "+i.ToString());
ChangeIntWithRef(ref i);
Debug.Log("Int with ref: "+i.ToString());
intList.Add(3);
intList.Add(2);
intList.Add(1);
Debug.Log("List size in the beginning: "+intList.Count);
ChangeListWithoutRef(intList);
Debug.Log("List size after passing without ref: "+intList.Count);
ChangeListWithRef(ref intList);
Debug.Log("List size after passing with ref: "+intList.Count);
}
void ChangeIntWithRef(ref int intByRef){
intByRef = 5;
}
void ChangeInt(int intWithoutRef){
intWithoutRef = 10;
}
void ChangeListWithRef(ref List<int> listWithRef){
listWithRef = new List<int>();
}
void ChangeListWithoutRef(List<int> listWithoutRef){
listWithoutRef = new List<int>();
}
}
Jamora: C++ (the OPs, and my, native language) also has pass by reference. But pass-by-ref doesn't allow another object to keep the reference, maybe change it later.
You know how just having a link to some other script, for use later, can be nice? In C++ you can also just have a link to any old float. In C# you can only link to something bigger that has that float in it.
Answer by Azrapse · Oct 24, 2013 at 07:44 PM
This solution let's you create something similar to what you want, while not using real pointers.
First, create a new C# script file called Pointer.cs and put this inside:
using System;
public static class Pointer
{
public static Pointer<T> Create<T>(Func<T> read, Action<T> assign)
{
return new Pointer<T>(read, assign);
}
}
public class Pointer<T>
{
public T Value
{
get { return getter(); }
set { setter(value); }
}
protected Func<T> getter;
protected Action<T> setter;
public Pointer(Func<T> getter, Action<T> setter)
{
this.getter = getter;
this.setter = setter;
}
public static implicit operator T(Pointer<T> pointer)
{
return pointer.Value;
}
}
Now you can use it anywhere in a simple way. I will rewrite your sample code:
public class MyClass
{
public bool bBool;
private Pointer<float> pData;
void Start()
{
if (bBool)
pData = Pointer.Create(()=> otherObject.fData, (value) => otherObject.fData = value);
else
pData = Pointer.Create(()=> differentObject.fData, (value) => differentObject.fData = value);
}
public void OtherFunction(float _fNewData)
{
pData.Value = _fNewData;
}
public float AnotherFunction()
{
return pData; // No need to specify pData.Value when returning it or assigning it to a float.
}
}
The important part is how you create the Pointer.
You call to Pointer.Create(readingLambda, assigningLambda)
for that, where readingLamda
is a lambda expression without parameters that returns the member from the object, and assigningLamda
is a lambdaExpression with a parameter that assigns the parameter to the member of the object.
For example, let's say I have a
class Person
{
string name;
int age;
}
and then in another part...
var john = new Person{name="John", age=21};
var mary = new Person{name="Mary", age=33};
var agePointer = Pointer.Create( ()=>john.age, (val) => john.age = val );
int theAge = agePointer; // theAge is now 21
agePointer.Value = 66; // john.age is now 66
agePointer = Pointer.Create( ()=>mary.age, (val)=>mary.age = val);
agePointer.Value = 15; // mary.age is now 15
This approach has the benefit (over real pointers) that the object owner of the field or property will never leave scope or be collected as long as your Pointer object itself exists. So no problems in that respect. :)
That allows each class to expose one pData.
What if one class has an array of pData, and another has two individual pData's ... and you want to be able to have a pointer to any of them? You could say that proper design shouldn't need that, but then it quickly gets into dogma.
Well, in that case you are changing the scenario you were asking about. Ins$$anonymous$$d of using such a generalist scenario, why don't you tell us what are you planning to use this for?
And it's not about dogma. Object Oriented principles aren't the Commandements of a sect we follow while wearing silky robes with nothing under and pray with our hands high in the air to Bjarne Stroustrup.
They are there because they come to limit some of the bad practices that led (and still lead) to unmaintanable code.
Sorry for the length of this comment, but I rather warn you now than let you suffer later on. :)
In C++, you can hack your way as much as you want, fast and easy. C++ will not keep you from digging your own grave, when you write some piece of code that later there is no way to debug without losing your precious brain cells while at it. C++ will just grin quietly at you while you write what you think it's a really smart piece of code that will stab you later while debugging.
What if you get your pointer to the float field as you wanted, and then the owner object is disposed, leaves scope, or is freed from memory or collected? You would end with a pointer to... nowhere from the other place in the code. You will have a lot of fun then.
What if, for some reason, you want to change the implementation of one of the classes that own the field; for example, changing it from being stored to being calculated. You would need to scan the code of the whole program searching for possible pointers to that field. What if you have a chain of pointers? Wohoo! It's a nightmare derived of a not well thought data structure design. Not because we can do something in a language it means we should do it. Otherwise, we would be using gotos all day long!
I could write here for you some little utility class that would allow you to simulate what you are asking for. But I really think you shouldn't bring with you the bad habits from another language to a language whose main principles are precisely to avoid those bad habits. You decide.
I have updated my answer with something much closer to what you wanted. Please, have a look.
Your answer
Follow this Question
Related Questions
Pointer or Copied Value? 1 Answer
Where to allocate class fields in a MonoBehaviour ? 1 Answer
C# Class Does Not Implement Interface Member 2 Answers
An Inheritance question. ( accessing non-static members from a script component) 2 Answers
Access to script global var's from a transform object? (specific ? on something that should work) 2 Answers