- Home /
Why is this generic "Get component if null" code not working
I find myself often doing this kind of pattern:
AudioSource cachedAudioSource = null;
AudioSource audioSource
{
get
{
if( cachedAudioSource == null )
{
cachedAudioSource = (AudioSource)GetComponent( typeof( AudioSource ) );
}
return cachedAudioSource;
}
}
That gets me the components the first time I need them; no more, no less.
So I thought I could use generics to clean up this code a little bit:
public static void GetComponentIfNull< T >( MonoBehaviour that, ref T cachedT ) where T : Component
{
if( cachedT == null )
{
cachedT = (T)that.GetComponent( typeof( T ) );
if( cachedT == null )
{
Debug.LogWarning( "GetComponent of type " + typeof( T ) + " failed on " + that.name, that );
}
}
}
Which results in my client code looking a little neater:
AudioSource cachedAudioSource = null;
AudioSource audioSource
{
get
{
Util.GetComponentIfNull( this, ref cachedAudioSource );
return cachedAudioSource;
}
}
But the problem is, it doesn't seem to work. Specifically, the check to see if cachedT == null in the generic function seems to think the ref pointer to my T object is NOT null (even if I explicitly set it to null, which shouldn't be necessary), yet if I check to see if audioSource == null, that returns true.
More specifically, it seems to only happen with built in types that don't derive from MonoBehaviour, i.e. audio sources colliders, etc. I've used this script successfully before for my own scripts.
Is this just something I can't work around due to the fact that apparently nulls aren't real nulls?
Edit: Thanks to @SpikeX for pointing out that Component.audio basically does what I want with my specific example, but I'm still somewhat curious as to why it doesn't work.
Answer by Mike 3 · Jul 06, 2010 at 08:35 AM
If you return the value instead, it'll skip the crazy behaviour you're seeing with the ref (And heck, added an overload now so you can pass any component or gameobject and it still works):
public static T GetComponentIfNull< T >( Component that, T cachedT ) where T : Component { if( cachedT == null ) { cachedT = (T)that.GetComponent( typeof( T ) ); if( cachedT == null ) { Debug.LogWarning( "GetComponent of type " + typeof( T ) + " failed on " + that.name, that ); } }
return cachedT;
}
public static T GetComponentIfNull< T >( GameObject that, T cachedT ) where T : Component { if( cachedT == null ) { cachedT = (T)that.GetComponent( typeof( T ) ); if( cachedT == null ) { Debug.LogWarning( "GetComponent of type " + typeof( T ) + " failed on " + that.name, that ); } }
return cachedT;
}
And implemented in your other code similar to before:
AudioSource cachedAudioSource = null;
AudioSource audioSource
{
get
{
cachedAudioSource = Util.GetComponentIfNull( this, cachedAudioSource );
return cachedAudioSource;
}
}
I've actually noticed the same thing in my own project (Though with textures instead of components) - it seems like unity's UnityEngine.Object references work a little wonky with being passed as null to ref parameters
An interesting solution. I'll probably implement it. I am kind of curious about the performance implications of assigning the variable on every get
, but it's probably negligible.
Extremely negilible - I think a function call alone is less performant than assigning a reference, never$$anonymous$$d the conditional check inside it
Answer by qJake · Jun 07, 2010 at 11:08 PM
The ref keyword requires the variable to be initialized before it can be passed, and (I believe) will do so automatically if it's found to be null. You want to use the out keyword instead.
You also have this method header:
public static void GetComponentIfNull< T >( MonoBehaviour that, ref T cachedT ) where T : Component
But you call the method like this:
Util.GetComponentIfNull( this, ref cachedAudioSource );
I think you mean to call the method like this:
Util.GetComponentIfNull<AudioSource>(this, ref cachedAudioSource);
Also, kind of a side note, why are you even bothering with this? MonoBehaviours maintain a list of common component references anyway, that basically do exactly what you're already doing (so essentially you're reinventing the wheel). Check out the Inherited Members section of the MonoBehaviour doc page which lists all the references that are built into the MonoBehaviour class. Checking these for null is a lot less costly than using GetComponent() on them, even if only once. So, for example, instead of using the code you produced, just check if the inherited property audio
is null or not. This is an automatically-maintained reference to the AudioSource component attached to this game object.
I do recognize that your method would work for any custom component type that isn't included as a reference in the MonoBehaviour class, though, and in those instances, it would be appropriate.
I overlooked the audio parameter (annoyingly it doesn't follow the same na$$anonymous$$g pattern as the other types), but that doesn't solve the issue at hand. There are some classes that don't have that in them. It really doesn't matter, in this case, whether or not I'm using ref vs. out. Since I'm using them for a reference type, I'm assu$$anonymous$$g that under the hood the type is similar to a C-style pointer and it's actually is "initialized" to null, otherwise it would throw a compile error.
Also, with respect to the type arguments in the generic method, those can be left out as they're inferred by the type passed in to the argument. http://msdn.microsoft.com/en-us/library/twcad0zb(VS.80).aspx
Also, are you sure about the built in properties being faster than caching them yourself? I've done some performance testing in the past with caching my own transform ins$$anonymous$$d of using the built in transform, and the former was faster. Also that goes specifically against what this forum thread says: http://forum.unity3d.com/viewtopic.php?p=187317
You still should cache them, but what he is saying is to use. cachedAudioSource = audio; ins$$anonymous$$d of cachedAudioSource = (AudioSource)GetComponent(typeof(AudioSource)); Your only calling it once, so the performance difference shouldn't be noticeable either way.
You also shouldn't optimize this much unless your game requires it in order to run well... have you ever heard the saying "Premature optimization is a sin"?
Answer by nullcodes.com · Jun 17, 2011 at 11:13 AM
Hello? Thankyous for Null code: link text There are some classes that don't have that in them. It really doesn't matter, in this case, whether or not I'm using ref vs. out. Since I'm using them for a reference type
Answer by nullcodes.com · Jun 17, 2011 at 11:14 AM
Hey ! Thankyous For me Nullcode :http://nullcodes.com/nulled-scripts/ There are some classes that don't have that in them. It really doesn't matter, in this case, whether or not I'm using ref vs. out. Since I'm using them for a reference type - Null codes - Null script
Answer by atulvi · Dec 16, 2020 at 11:45 AM
Any Unity Component Check null or not. [Not a Generic way but its help to you]
1) Create Script : InternalComponentExtension.cs
public class InternalComponentExtension : MonoBehaviour
{
[SerializeField]private static TextMeshProUGUI textMeshProUGUI;
[SerializeField]public static TextMeshProUGUI TextMeshProUGUI
{
get{
if(textMeshProUGUI == null)
{
GameObject g1 = new GameObject();
textMeshProUGUI = g1.AddComponent<TextMeshProUGUI>();
}
else
{
Debug.LogWarning("Please Pass Orignal Component "+typeof(TextMeshProUGUI));
}
return textMeshProUGUI;
}
}
[SerializeField]private static AudioSource audioSource;
[SerializeField]public static AudioSource AudioSource
{
get{
if(audioSource == null)
{
GameObject g1 = new GameObject();
audioSource = g1.AddComponent<AudioSource>();
}
else
{
Debug.LogWarning("Please Pass Orignal Component "+typeof(AudioSource));
}
return audioSource;
}
}
}
2) Access or Check Component Create Script : Demo.cs and add in to any GameObject in scene.
public class Demo: MonoBehaviour
{
[SerializeField]private TextMeshProUGUI cookingNote;
[SerializeField]private TextMeshProUGUI CookingNote
{
get
{
if(cookingNote == null)
{
cookingNote = InternalComponentExtension.TextMeshProUGUI;
}
return cookingNote;
}
}
[SerializeField]private AudioSource audioSource;
[SerializeField]private AudioSource AudioSource
{
get
{
if(audioSource == null)
{
audioSource = InternalComponentExtension.AudioSource;
}
return audioSource;
}
}
void Start()
{
//Access any Renderer or TextMeshProUGUI property.
CookingNote.text = "It is Null";
AudioSource.volume = 0.5f;
}
}
Your answer
Follow this Question
Related Questions
Unity GetComponent return value 2 Answers
How to Get TextMesh component from the Gameobject the script is attached to? 3 Answers
Using GetComponent in multiple scripts for same component? C# 2 Answers
referencing a component of a gameobject 1 Answer
Checking if a component has been removed or not at runtime 1 Answer