I really love readonly keyword, but I have problems when using it in Unity.
You can only set a readonlyvariable 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 readonlyyou 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?
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 injectedreadonly 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).
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.
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?
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.
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.
I know this topic has long been dead, but as it seems that the situation has (on Unitys side) not yet changed, let me offer another approach.
I have a Faction class, which is deriving from ScriptableObject. I want to be able to check whether or not a the ownership of an area is by either more than one Faction or by none. So I wanted to implement something like int.MaxValue for my Factions. This is how I went about it:
I have overridden the Equals ( object other ) method, as well as defined custom == and != operators.
When checking for equality I strictly go by the name of the Factions. Equal names mean equal Faction for me.
There are two public static properties that offer a Faction.None and a Faction.Multiple instance.
Now I only have to make sure that my players won’t name their factions None or Multiple. I guess the easiest way is to have some kind of ValidateMe () method inside the Faction class.
I have to admit though: I just implemented this. Can’t really say how useful it is.
Here’s the complete class:
public class Faction : ScriptableObject
{
public const string MULTIPLE = "Multiple";
public const string NONE = "None";
public static Faction Multiple
{
get
{
Faction result = CreateInstance<Faction> ();
result.factionName = MULTIPLE;
result.colour = Color.gray;
return result;
}
}
public static Faction None
{
get
{
Faction result = CreateInstance<Faction> ();
result.factionName = NONE;
result.colour = Color.black;
return result;
}
}
[ SerializeField ]
private string factionName = null;
public string FactionName
{
get { return factionName; }
set { factionName = value; }
}
[ SerializeField ]
private Color colour = Color.white;
public Color Colour
{
get { return colour; }
set { colour = value; }
}
public override bool Equals ( object other )
{
Faction otherFaction = other as Faction;
if ( null == otherFaction )
{
return false;
}
return otherFaction.factionName == factionName;
}
public static bool operator == ( Faction lha, Faction rha )
{
return lha.factionName == rha.factionName;
}
public static bool operator != ( Faction lha, Faction rha )
{
return lha.factionName != rha.factionName;
}
}