| Keywords | struct, class, ref, out |
|---|---|
| Category | Type system |
| In Unity | Vector3, Quaternion, Color are structs; GameObject, Transform, your scripts are classes |
Value & Reference Types
Every type in C# is either a value type (copied when you assign it) or a reference type (shared when you assign it). This one difference explains many confusing Unity errors, like why transform.position.x = 5 doesn't compile.
A value type is a photocopy of a document. Give someone a copy and they can scribble on it all they like; your original doesn't change. A reference type is a link to a shared online document. Give someone the link and you're both looking at the same document, so their edits show up for you too.
Which is which?
| Value types (copied) | Reference types (shared) |
|---|---|
int, float, bool, double, char | string (special: behaves like a value, see below) |
enum types | Arrays (int[], GameObject[]) |
Any struct: Vector2, Vector3, Quaternion, Color, Ray, RaycastHit | Any class: GameObject, Transform, Rigidbody, List<T>, your MonoBehaviours |
Can't be null (unless you use int?) | Can be null |
Seeing the difference
// Value type: b gets its own copy
Vector3 a = new Vector3(1f, 2f, 3f);
Vector3 b = a;
b.x = 100f;
Debug.Log(a.x); // 1 (a is unchanged)
// Reference type: both variables point to the same object
List<int> listA = new List<int> { 1, 2, 3 };
List<int> listB = listA;
listB.Add(4);
Debug.Log(listA.Count); // 4 (it's the same list)
Why transform.position.x = 5 fails
transform.position is a property that returns a Vector3. Because Vector3 is a struct, you get back a copy. Changing x on a temporary copy would do nothing, so the compiler stops you with error CS1612.
// Error CS1612: Cannot modify the return value of 'Transform.position' because it is not a variable
transform.position.x = 5f;
// Fix: copy, change, assign back
Vector3 pos = transform.position;
pos.x = 5f;
transform.position = pos;
The same applies to rb.linearVelocity, material.color and any other struct property.
Passing to methods
void MakeBig(Vector3 v) { v *= 10f; } // changes a local copy only
void MakeBig(ref Vector3 v) { v *= 10f; } // changes the caller's variable
void Rename(GameObject go) { go.name = "Big"; } // changes the shared object
Vector3 size = Vector3.one;
MakeBig(size); // size is still (1,1,1)
MakeBig(ref size); // size is now (10,10,10)
Rename(gameObject); // the real GameObject is renamed
| Keyword | Meaning | Unity example |
|---|---|---|
ref | Pass the caller's variable itself, so the method can change it | Vector3.SmoothDamp(..., ref velocity, ...) remembers speed between frames |
out | The method must fill this variable in. Used to return extra results | Physics.Raycast(ray, out RaycastHit hit), TryGetComponent(out Rigidbody rb) |
Strings are special
string is a reference type, but it's immutable: you can never change a string, only make a new one. So it behaves like a value type in practice.
string a = "Hello";
string b = a;
b += " World"; // creates a NEW string; a is still "Hello"
Making your own struct
Use a struct for small bundles of data that you treat as a single value (like a coordinate or a damage number). Use a class for everything else.
[System.Serializable] // lets Unity show it in the Inspector
public struct DamageInfo
{
public float amount;
public Vector3 hitPoint;
public bool isCritical;
public DamageInfo(float amount, Vector3 hitPoint, bool isCritical)
{
this.amount = amount;
this.hitPoint = hitPoint;
this.isCritical = isCritical;
}
}
// Usage
var info = new DamageInfo(25f, hit.point, Random.value < 0.1f);
target.TakeDamage(info);
What happens at runtime
- Value types are stored directly where they're declared: inside a local variable, or inline inside the object that has them as a field. Local value types need no garbage collection.
- Reference types live on the heap, a shared memory area. Your variable only holds the address.
newon a class allocates heap memory, which the garbage collector must clean up later. - This is why Unity's math types are structs: you can create millions of
Vector3s per second without producing garbage. - Boxing: if you store a value type in an
objectvariable (or a non-generic collection), C# wraps it in a heap object. That creates garbage. Generic collections likeList<int>avoid it.
Common Mistakes
- Modifying a struct inside a list:
points[0].x = 5fails forList<Vector3>for the same reason astransform.position.x. Copy, change, assign back:var p = points[0]; p.x = 5; points[0] = p;(Arrays allow it directly:pointsArray[0].x = 5works.) - Expecting a copy of a class.
List<Item> backup = inventory;is not a backup; it's the same list. Usenew List<Item>(inventory). - Big structs. Structs are copied every time you pass them, so keep them small (a handful of fields).
See also
This page is a simplified, AI-assisted interpretation. For the complete and authoritative reference, see the official Microsoft C# documentation.