- Home /
readonly C# keyword not so usefull in Unity
I really love readonly
keyword, but I have problems when using it in Unity.
You can only set a readonly
variable when you declare it or in its class constructor, but you don´t normally use constructors with MonoBehaviour derived classes so its uses are much more limited. Private set is not as powerfull as true immutability, and with a good use of readonly
you could also set it with the constructor while making it immutable!
Should Unity provide a solution for this? It would be really nice that Start
or Awake
allowed you to set readonly
variables, but I don´t think that´s possible.
So, is there a way around for copying the behaviour that readonly
provides?
Answer by Statement · Dec 09, 2013 at 02:59 PM
Currently, Unity does not support readonly
field usage in a meaningful and straight forward ways to use. If you are working with dependency injection and want to use constructor injection then unfortunately I think you are not getting anywhere. The only way you can do this is by relying on Unity to do it for you, since Unity constructs the components and not you. Unity supports field injection, but that's the extent of serializable behaviour that Unity supports to my knowledge.
It would be nice if Unity also injected readonly
fields and that there was some mechanism to define the values via editor (or inject the values during construction).
Since the readonly
fields will not serialize to an asset file, it means that the only way you can populate readonly
fields is through reflection at application runtime. It would be far more desirable to be able to do this in the editor, up front.
For now, try to let go over your wishes to use readonly
and instead try to adapt to the constraints that Unity place on your code design. Accept that you can't for now use readonly
in classes subclassing UnityEngine.Object
(such as UnityEngine.MonoBehaviour
or UnityEngine.ScriptableObject
to name a few).
If you really want to use readonly
, be prepared to set up an injection framework that deals with runtime injection of content, as well as some object that describes which instance should have what value for a given field. It gets messy quickly, since you now have to also perform runtime injection on objects you tell Unity to construct via GameObject.AddComponent
, Object.Instantiate
, Resources.Load
etc etc.
And be aware that asking Unity to construct one thing may cause Unity to construct many things. The obvious one is prefabs. If you Instantiate a prefab, you can't tell in advance what else is going to be on there. Maybe you have multiple scripts that require injection. A less obvious one is AddComponent, where adding a component may also add a required component (via `RequireComponent`).
Amazing answer, it´s good that you wrote this. Everybody should ignore readonly for the moment... As you said it would be nice that Unity $$anonymous$$m included this, I don´t know how difficult it can be but it would be a great adition.
Since effectively you are configuring how to construct an object via the editor, you can think of the editor is preparing your Composition Root for you. If we can't have constructors, then at least it would indeed be nice if we could allow the editor to set up values for readonly
fields and at runtime populate them from the serialized values. It would in effect behave almost the same as constructor injection, although you can't place constraints on the values injects (like, traditionally you could force throw ArgumentNullException if a required dependency was not passed to the constructor). It also gives you a way to define clearly what is intended to be set up in advance for us (via editor/designers) while clearly communicating that you shouldn't alter the structure at runtime since the readonly keyword will enforce the constraint at compile time.
Answer by Bunny83 · Dec 09, 2013 at 11:15 AM
Readonly is just another compile time restriction just like private is. If someone has access to your source he can change private to public or remove the readonly. If he hasn't access to your source private and readonly gives you the same result in the sense of write-access.
If the user of your class uses reflection, everything can be altered at runtime except consts since they are compile time constants.
I am more worried about damage that I can cause, not so much about other people. I know that I can forget if I should set a variable or not, but the compiler won´t.
Sure, but when you think about it, Unity uses the default $$anonymous$$ono C# compiler. All code could even be compiled with the .NET compiler and you just import the assemblies. Awake and Start are usual methods which can be called / recalled at any time. This would make it quite pointless to restrict the initializarion to those methods.
If you want you can still use readonly and use reflection to set the value once in Awake or Start. Unity uses a lot reflection itself when it comes to serialization.
@$$anonymous$$rVerdoux, Unity does not allow you to set readonly
fields because Unity doesn't allow you to construct UnityEngine.Objects
. Since readonly
fields must be set in the constructor, we can see the problem and conclude that this problem is by design.
@Bunny83, that's one interesting idea you have there with reflection. It don't know how much it would be worth the hassle to go about doing it during Awake and at runtime, but if the readonly
fields also serialize, then things might become a lot more attractive since you can inject the dependencies via editor scripts. I'm going to test this soon :)
Edit: I found that Unity does not serialize readonly fields into the scene, so you can't depend on Unity to serialize readonly fields to become automatically populated when Unity constructs the component, sadly.
// Does not serialize into the .unity scene file
public readonly int testField;
// Does not serialize into the .unity scene file
[SerializeField]
public readonly int testField;
// Does not serialize into the .unity scene file
// Does not use $$anonymous$$yCustomPropertyDrawer for drawing either
[SerializeField]
[$$anonymous$$yCustomPropertyDrawer]
public readonly int testField;
So you seem out of luck with that approach. It would however be nice if we could get this functionality by asking the editor/core $$anonymous$$m at Unity nicely via feedback that we want this.
Answer by ArkaneX · Dec 09, 2013 at 10:18 AM
IMHO private/protected set is good enough to use instead. If you want to have more readonly like behavior, you could introduce additional boolean, indicating if particular variable was set. Something like this:
private bool _isMyVariableInitialized = false;
private string _myVariable;
public string MyVariable
{
private set
{
if(_isMyVariableInitialized)
{
throw new Exception("Property has already been initialized.");
}
_myVariable = value;
_isMyVariableInitialized = true;
}
get
{
if(!_isMyVariableInitialized)
{
throw new Exception("Property has not been initialized.");
}
return _myVariable;
}
}
But the question is: do you really need such complication? :)
I agree with you that I don´t need such complication. If I had to do this I´d prefer private set, but if I could choose, private immutability is still my favourite. I find it very expresive, it´s a constant that you can set at construction time, I really like it.
edit: and even with this method, you can still acess _myVariable privately without using the property, and the boolean as well, so I don´t think it mimics very well readonly
.
I agree that this is not a bulletproof solution, but I'm afraid you won't find any that really is...
I´m afraid I agree with you. I´ll just use private set, which is pretty solid anyway, this question was just to find out people´s solutions, in case anybody had already thought of using readonly in with monobehaviour.
Answer by Jamora · Dec 09, 2013 at 10:55 AM
You will need to separate your data (more accurately: state) from your MonoBehaviours. Use a normal (non-MB) class to store your readonly data, and have a reference to that object in your MB.
[RequireComponent(typeof(Rigidbody))]
public class NewBehaviourScript : MonoBehaviour {
public MyData data;
void Awake(){
data = new MyData(rigidbody);
}
public Rigidbody GetReadOnlyRigidbody(){ return data.rigidbody; }
void Reset(){
data = new MyData(rigidbody);
}
private class MyData {
public readonly Rigidbody rigidbody;
private MyData(){}
public MyData(Rigidbody rb){
rigidbody = rb;
}
}
}
The only way for damage to change during runtime, when doing it like this, is if another developer extends MyData, changes the constructor and substitutes the original fioeld with the extended instance. As a precaution, you could seal MyData (using the sealed
keyword), but that's only effective if the source code can't be accessed by other developers (they could just remove the sealed keyword).
Edit:
This solution does prevent the developer from messing up the value. Altough, I concede that it's not 100% idiot proof. If I ever needed a readonly field, this is most likely how I'd go about it.
The readonly values are set in the constructor, so even if you instantiate a new MyData, the value still can not be null. Because the field is readonly inside MyData, it cannot be changed during runtime from elsewhere in the code (well,reflection). The only way for a developer to mess this up, is to give the object a wrong RigidBody, say from another gameObject, but that sort of an error would probably have happened even if Unity allowed constructors in MBs.
I do agree with Bunny in that having only a getter is enough, more often than not.
That´s smart. It does´t seem ideal, but at least this gets readonly to work properly. Thank you!
Yes, this is a solution of some sort, but it's a bad example ;) readonly for primitive types works well even in Unity. The problem are reference types to components and other Unity classes which can't be created / accessed in the constructor of the $$anonymous$$onoBehaviour and have to be initialized later.
As the example is at the moment it doesn't secure the value at all ;)
From outside Enemy you can't mess up the value, but from inside you can still replace "data" at any time or even set it null.
Sure, why would you do this... but the point was to restrict yourself from not be able to mess up the value. This is more an attempt in the category "security through obscurity". It adds complexity but no security.
In the end it always depends on your discipline. A "readonly" property (only a getter) and a private variable are in most cases more than enough. If the class is designed right you should use the interface it provides.
This very simple example could have been solved with const.
@Bunny83 You are completly right, I didn´t fully understood the code! This causes the same problem as before... I´m sorry Jamora but I will take the correct answer from you. As Bunny83 is saying things can still be set privately, and not only that, now the setters are in other class so it´s a lot more obscure.
Answer by yoyo · May 22, 2014 at 04:21 PM
As others have mentioned, you can use the readonly keyword just fine in classes that do not subclass from MonoBehaviour or ScriptableObject, as this is just regular C# usage.
For types derived from MonoBehaviour and ScriptableObject, the lack of a constructor means readonly is effectively similar to const -- you can provide an initializer, but you don't get a chance to modify the value in a constructor.
However, since readonly fields are accessible through reflection, it is possible to modify their values after construction (though this violates the intent of the keyword). Here is an extension class that lets you modify any field of any object, including readonly and private fields:
public static class FieldSetterExtension
{
public static void SetField<T>(this object obj, string field, T val)
{
obj
.GetType()
.GetField(field, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.SetValue(obj, val);
}
}
With this compiled into your project you can now do things like:
public class ReadOnlyComponent : MonoBehaviour
{
public readonly int CantTouchThis = 1;
void Awake()
{
this.SetField<int>("CantTouchThis", 42);
}
}
Yes, this is a dangerous extension method that will let you do all sorts of nefarious things, use at your own risk!
The problem remains that Unity does not serialize read-only fields, so the CantTouchThis field above cannot be edited in the Inspector. Since readonly fields are accessible via reflection, there's no particular reason Unity shouldn't be able to serialize them, and it seems to me like a good feature to request.