I’m pretty sure you just stumbled on that little “null trick” Unity implements for all objects derived from UnityEngine.Object.
As you might know objects derived from UnityEngine.Object usually have a native counterpart on the C++ side of the engine. Since C# uses a managed memory space and C++ not there are some problems with this object “duality”. In C++ (and Unity) you can actually “destroy” / free an object manually. In C# that’s not possible since the managed memory is released when all references to an object go out of extent.
Now we have the method Destroy which can destroy all objects derived from UnityEngine.Object. So how does this work in the managed environment? The answer is: it actually doesn’T work. It’s not possible to destroy a managed object. When you call Destroy on a Component Unity just destroys the native part on the C++ side. The managed object is still there until all references to that object are gone.
Unity uses a little “trick” to make a reference to such a “dead object” appear null by simply overloading the Equals method and == operator. So when you compare the reference to null they will return true, even the reference isn’t null. Though since the object is actually dead you can’t use it anymore.
The same thing happens to MonoBehaviours objects that never had a native part. This happens when you create an instance of a MonoBehaviour with the “new” keyword. This will create a managed object, but since the component isn’t attached to a GameObject it’s in it’s dead state from the very beginning.
So first rule No1: Never use the new keyword on MonoBehaviour derived classes.
btw:
This comparison makes no sense at all:
if(obj.Equals(null))
If obj is actually null this will throw a NullReferenceException since you can’t call Equals on a null object. It’s possible to call it on a “fake-null” object since you actually have an object.
Since the == operator is not a virtual method you will notice a difference when using a variable of a type derived from UnityEngine.Object and a variable of type System.Object:
SomeMonoBehaviour A = new SomeMonoBehaviour(); // create fake null object
Debug.Log("A == null: " + (A == null));
Debug.Log("A.Equals(null) : " + (A.Equals(null)));
object B = A; // assign the same reference to an System.Object variable
Debug.Log("B == null: " + (B == null));
Debug.Log("B.Equals(null) : " + (B.Equals(null)));
The result will be:
"A == null: true"
"A.Equals(null) : true"
"B == null: false"
"B.Equals(null) : true"
So knowing that little fact you can test a “seamingly” null reference for being fake null by casting it into “object” do a == check for null and an Equals check afterwards like this:
bool IsReferenceFakeNull(object aRef)
{
return aRef != null && aRef.Equals(null);
}
This method is safe for any reference as true null values won’t trigger the Equals check which would cause a null-ref-exception otherwise.