There are already a few answers here, but here's answer that takes Unity3D into account (the answer is very specific to Unity3D, I'd do most of this very differently in most contexts):
In general, Unity3D doesn't use exceptions traditionally. If you throw an exception in Unity3D, it's nothing like in your average .NET application, namely it won't stop the program, t most you can configure the editor to pause. It'll just get logged. This can easily put the game in an invalid state and create a cascade effect that makes errors difficult to track down. So I'd say in the case of Unity, letting Add
throw an exception is an especially undesirable option.
But on the examining the speed of exceptions isn't a case of premature optimization in some cases because of how Mono works in Unity on some platforms. In fact, Unity3D on iOS supports some advanced script optimizations, and disabled exceptions* is a side effect of one of them. This is really something to consider because those optimizations haven proven very valuable for many users, showing a realistic case for considering limiting the use of exceptions in Unity3D. (*managed exceptions from the engine, not your code)
I'd say that in Unity you might want to take a more specialized approach. Ironically, a very down-voted answer at the time of writing this, shows one way I might implement something like this specifically in the context of Unity3D (elsewhere something like this really is unacceptable, and even in Unity it's fairly inelegant).
Another approach I'd consider is actually not indicating an error far as the caller is concerned, but rather using the Debug.LogXX
functions. That way you get the same behavior as throwing an unhandled exception (because of how Unity3D handles them) without risking putting something in an strange state down the line. Also consider if this really is an error (Is trying to load the same material twice necessarily an error in your case? Or might this be a case where Debug.LogWarning
is more applicable).
And in regards to the using things like Debug.LogXX
functions instead of exceptions, you still have to consider what happens when an exception would be thrown from something that returns a value (like GetMaterial). I tend to approach this by passing null along with logging the error (again, only in Unity). I then use null checks in my MonoBehaviors to ensure any dependency like a material is not a null value, and disable the MonoBehavior if it is. An example for a simple behavior that requires a few dependencies is something like this:
public void Awake()
{
_inputParameters = GetComponent<VehicleInputParameters>();
_rigidbody = GetComponent<Rigidbody>();
_rigidbodyTransform = _rigidbody.transform;
_raycastStrategySelector = GetComponent<RaycastStrategySelectionBehavior>();
_trackParameters =
SceneManager.InstanceOf.CurrentSceneData.GetValue<TrackParameters>();
this.DisableIfNull(() => _rigidbody);
this.DisableIfNull(() => _raycastStrategySelector);
this.DisableIfNull(() => _inputParameters);
this.DisableIfNull(() => _trackParameters);
}
SceneData.GetValue<>
is similar to your example in that it calls a function on a dictionary that throws an exception. But instead of throwing an exception it uses Debug.LogError
which gives a stack trace like a normal exception would and returns null. The checks that follow* will disable the behavior instead of letting it continue to exist in an invalid state.
*the checks look like that because of a small helper I use that prints out a formatted message when it disables the game object**. Simple null checks with if
work here (** the helper's checks are only compiled in Debug builds (like asserts). Using lambdas and expressions like that in Unity can take it's toll on performance)
_Materials.Add
throw an exception?string.Format
over string concatenation for building the exception message. 2) only useas
if you expect the cast to fail and check the result fornull
. If you expect to always get aMaterial
, use a cast like(Material)Resources.Load(...)
. A clear cast exception is easier to debug than a null reference exception that occurs later on.LoadMaterialIfNotLoaded
orReloadMaterial
(this name could use improvement) method useful.