The short answer is that it's all about type safety, and how we navigate type guarantees.
Casting like this:
(TypeIWant)SomeMethodThatReturnsAMoreGeneralType()
will throw an error if the value returned from the method isn't convertible to the TypeIWant
.
But using the as
keyword:
SomeMethodThatReturnsAMoreGeneralType() as TypeIWant
will instead populate the value with null
if the conversion fails. So if there's a legitimate reason you might get a different return type for your particular application, using as
lets you detect and handle that case gracefully, without throwing errors and try/catch
complications.
There's no significant performance difference between the two in modern C#, so the main function here is to clearly show your intent and trap errors where you want them. If your game absolutely expects TypeIWant
here, then use a cast. If that assumption is ever violated, you'll get the error immediately and can go investigate right at the source.
On the other hand, as
shows that you knowingly expect the type to sometimes be something else, and that this is not immediate cause for concern. It will be the job of the subsequent code to check for nulls where there shouldn't be nulls and fire a corresponding error if they need to.
The last case is due to the fact that even if we write GameObject.Instantiate
, it's still calling UnityEngine.Object.Instantiate
, which is made for cloning anything that derives from UnityEngine.Object
- that means it can't guarantee you're going to get a GameObject
out at the end. GameObject.Instantiate(GetComponent<Renderer>().sharedMaterial)
is also valid syntactically, returning a Material. So we need a cast or as
to convert the return value to a GameObject
before we can store it in a variable with type GameObject
.
Of course you could also skip the cast entirely, and use the generic form or Instantiate/Load if you know in advance what type you want. That way a mismatch is a compile-time error, rather than a runtime error, and can often be faster to catch & correct:
TypeIWant myThing = Instantiate<TypeIWant>(Resources.Load<TypeIWant>(path));
Wherever possible, I like to type my variables according to the most specific type I need from them. So rather than storing a bullet prefab as a GameObject or Transform, I'll give it a Projectile
component and expose a slot for it like:
public Projectile bulletPrefab;
Then I can instantiate it with:
var bullet = Instantiate<Projectile>(bulletPrefab);
Now I have strong type guarantees throughout, without casts, and the Inspector will enforce that whatever I assign to bulletPrefab
has the required component. (Don't worry, Instantiate will still clone the whole prefab, game object, other scripts, child hierarchy, and all - not just a floating Projectile component)