- Home /
Pass ScriptableObject field into a function by reference
I have a ScriptableObject
public class Data : ScriptableObject
{
public string dataToUse;
}
I can pass Data as a parameter, and it properly works as a reference
public class UsesData
{
Data data;
public UsesData(Data data)
{
this.data = data;
}
public void HandleData()
{
//This works just fine. It updates the Data instance field by reference
data.dataToUse = "newData";
}
}
However, I would like to pass the field itself as a reference, instead of the ScriptableObject
public class UsesData
{
string dataToUse_Ref;
public UsesData(string dataToUse)
{
this.dataToUse_Ref = dataToUse;
}
public void HandleData()
{
//This is only passing by value
dataToUse_Ref = "newData";
}
}
Then, a client can pass in the field, instead of the entire object
public class Client
{
//Assume Data is initialized before-hand
Data data;
UsesData usesData;
public Client()
{
usesData = new UsesData(data.dataToUse);
//This should update the Data field by reference, but does not because of passed-by-value
usesData.HandleData();
}
}
I fiddled around with using the ref keyword, but it did not work
public class UsesData
{
string dataToUse_Ref;
public UsesData(ref string dataToUse)
{
this.dataToUse_Ref = dataToUse;
}
public void HandleData()
{
dataToUse_Ref = "newData";
}
}
public class Client
{
Data data;
UsesData usesData;
public Client()
{
usesData = new UsesData(ref data.dataToUse);
usesData.HandleData();
}
}
Not sure if I am missing some syntactical sugar, or if this approach is just not possible for some reason. It seems like the ref keyword "should" work for something like this.
Answer by Bunny83 · Oct 13, 2019 at 01:42 AM
This is not possible. Yes the ref keyword actually passes a true managed "pointer" to the memory location where the field is stored to the method that has a ref parameter. However the ref parameter can only be used inside that method. Such a ref-pointer can not be "stored" for later use. Something like that is only possible with true unsafe code which is not recommended and also isn't supported by many target platforms.
Even strings are reference types (since they are objects and allocated on the heap), strings are immutable objects in C#. So once created they can not be changed. If you need the control flow you showed, your only real option is to pass the wrapping object as parameter and have your method modify the field of that referenced object.
An alternative is to use get and set delegate methods to carry out your change. This essentially uses a wrapping class (a closure object) though it all happens implicitly. Something like that:
public class UsesData
{
System.Func<string> getData;
System.Action<string> setData;
public UsesData(System.Func<string> aGetMethod, System.Action<string> aSetMethod)
{
getData = aGetMethod;
setData = aSetMethod;
}
public void HandleData()
{
setData("newData");
}
}
You would use it like this:
usesData = new UsesData(()=>data.dataToUse, d=>data.dataToUse = d);
usesData.HandleData();
Answer by benapprill · Oct 13, 2019 at 03:26 AM
A shout-out to @Bunny83, who gave a great response on the technicalities of using the ref keyword.
As he stated, the use of the ref keyword I was originally trying to use does NOT work for storing a reference for later use.
I managed to find a solution for my problem by using SerializedProperties and SerializedObjects.
public class Data : ScriptableObject
{
public string fieldToUse;
SerializedObject objSerialized;
public SerializedProperty fieldToUseSerialized;
private void OnEnable()
{
objSerialized = new SerializedObject(this);
fieldToUseSerialized = objSerialized.FindProperty("fieldToUse");
}
}
public class UsesData
{
SerializedProperty fieldToUseSerialized;
public UsesData(SerializedProperty property)
{
fieldToUseSerialized = property;
}
public void HandleData()
{
fieldToUseSerialized.stringValue = EditorGUILayout.TextField(fieldToUseSerialized.stringValue);
fieldToUseSerialized.serializedObject.ApplyModifiedProperties();
}
}
public class Client
{
//ScriptableObject, initialized through inspector somehow
Data data;
UsesData usesData;
public Client()
{
usesData = new UsesData(data.fieldToUseSerialized);
//Changes to the Data field now take affect through the SerializedProperty
usesData.HandleData();
}
}
Basically, instead of passing in the data field directly, I bound it to a SerializedProperty, and affected the change through that. This works fine for a SerializedObject, but a normal class that cannot be serialized like this, will still run into the same issue of not being able to store a reference for later.
This doesn't make much sense on several levels. First of all a SerializedObject instance will hold a reference to the serialized object. A SerializedProperty will hold a reference to it's SerializedObject. Both actually perform the serialization on the native code side through reflection. So this is much more complex than anything I had mentioned.
Though your main issue is that SerializedObject and SerializedProperty are editor classes which can not be used at runtime in a build. Since your Data class contains a field of type SerializedProperty the class can not be used at runtime. So this answer seems really strange. Even if this is a question about pure in editor code (which you have only mentioned in the tags), your Data class can not be used at runtime.
The main question is why do you want to pass in a string field anonymously to that method. If it's just for editor code, then yes, SerializedProperty is the way to go, but it's completely wrongly placed in your ScriptableObject. All in all your question description is way too abstract to deter$$anonymous$$e what your usecase is.
Yes, my goal is more for Editor code. I am trying to separate my Editor Window view code from the rest of it. For that, I am trying to bind the input fields to my data object.
I suppose you are correct, and my answer is too specific for my question. The question was asking how to do the reference at any time, not just outside of runtime in the Editor.
While my solution works, it requires extra serialization code. It winds up being a bit easier to just use the ref keyword as part of a function parameter. And at that point... breaking out the binding to its own class becomes unnecessary.
Your answer
Follow this Question
Related Questions
Scriptable object keeps referencing the objects? 1 Answer
Do you have to set a object reference for every script? 0 Answers
[CreateAssetMenu] For inherited ScriptableObjects 1 Answer
creating scriptableObject with data from scene in editor 0 Answers
Scriptable Objects, how to force Include in Compile / Build without referencing it in the scene? 1 Answer