Access Interface method : "TryGetComponent" or "MonoBehaviour is IInterfacable" ?

Hi everyone,
starting to work with interfaces, I wondering about what I should use to access the interface method?

In the unity learning section, it uses an example where it gets all MonoBehaviours and then access it.
Though, in the example it gets all MonoBehaviours in the scene, whereas I do a raycast.

But I also stumbled across another way, which is via the “TryGetComponent”.

So my two codes would look like this:
MonoBehaviuor:

if (hit.collider.GetComponent<MonoBehaviour>()) {
                    MonoBehaviour M = hit.collider.GetComponent<MonoBehaviour>();
                   
                    if (M is IArrowable && M is not IInteractable) {
                        //FALSE "collided" (e.g. treasure boxes will not be arrowed)
                        IArrowable g = (IArrowable)hit.collider.GetComponent<MonoBehaviour>();
                        g.Method();
                    }
                }

TryGetComponent:

if(hit.collider.gameObject.TryGetComponent(out IArrowable o) &&
                    !hit.collider.gameObject.TryGetComponent(out IInteractable i)) {

                    o.Method;

                }

(The two codes are just as an example)

I believe “GetComponent()” and the casting to (IArrowable) may be unneccessary costly.
Also, what is the “is” and “is not” which I’ve seen the first time? What is the difference to == and !=.

In the other method, I’m wondering if it matters that I have an (out IInteractable i) but not using it?

As I went over in a thread about literally the same thing today you can use interfaces with GetComponent<T>.

TryGetComponent(out T) is more or less the same thing that just does the null check inside the method, and returns true or false if there was a hit or not. It makes for cleaner code and has some performance benefits as well.

If you are concerned about cost, you should meticulously avoid writing code like this:

if(hit.collider.gameObject.TryGetComponent(out IArrowable o) &&
                    !hit.collider.gameObject.TryGetComponent(out IInteractable i))

This violates the DRY principle, which can become detrimental to performance but it’s also an effective way to keep your code maintainable and readable, which is far more important actually (the performance benefit is merely a positive side effect):

var target = hit.collider.gameObject;
if(target.TryGetComponent(out IArrowable o) &&
                    target.TryGetComponent(out IInteractable i) == false)

Also I can’t help it but for years now I’ve forced myself to avoid the ! negation operator (except in != comparison or when flipping bools) for the simple reason that it is so easy to skip over while reading the code. Especially if it’s followed by certain characters or comes in a bunch of conditions (bogus example):

if ((levelPlayed[i] || !IsConnected) && (IsConnected || !levelPlayed[i-1]))

More readable:

if ((levelPlayed[i] || IsConnected == false) && (IsConnected || levelPlayed[i-1] == false))

The is operator can be used to check if an object is castable to a particular type.

object something = "Hello!";
if(something is int)
{
    Debug.Log("The 'something' variable contains an integer value.");
}

if(something is float)
{
    Debug.Log("The 'something' variable contains a floating-point value.");
}

if(something is string)
{
    Debug.Log("The 'something' variable contains a string object.");
}

You can also use the is operator to both check if an object is castable to a particular type, and if it is, then cast it and assign it into a new variable.

if(something is string text)
{
    Debug.Log($"The 'something' variable contains the string \"{text}\".");
}

You can also use the is operator to test if something is null or not.

object maybeSomething = null;

if(maybeSomething is null)
{
    Debug.Log("The 'maybeSomething' variable does not contain any object.");
}

if(maybeSomething is not null)
{
    Debug.Log("The 'maybeSomething' variable does contain some object.");
}

The == operator on the other hand can be used to test if an object is equal to another object (not a type, but an instance of a type).

One special property of the == operator is that any class can override it with a custom implementation.

For example the String class overrides it, so that it returns true for two strings if their character arrays have identical contents, even if they actually refer to different objects in memory.

string a = new String("Hello!");
string b = new String("Hello!");
if(a == b)
{
    Debug.Log("The 'a' and 'b' variables both contain the same string value.");
}

However, the overloaded == operator will not get used if the instance is contained in a variable of a base type or an interface type.

object a = new String("Hello!");
object b = new String("Hello!");
if(a == b) // uses the == operator in the object class, not the overloaded == operator in the string class
{
    Debug.Log("The 'a' and 'b' variables both contain the same string value."); // not executed
}

This is important to know when dealing with UnityEngine.Object-derived classes (components, game objects, scriptable objects etc.). The == operator in the Object class has been overloaded to return true when a destroyed Object is compared against a null value. But if a destroyed Object is stored inside an interface type variable, or an object type variable, then comparing it against null returns false instead.

Player player = GetComponent<Player>();
Destroy(player);
if(player == null)
{
    Debug.Log("player == null"); // this gets printed
}

if(player is IPlayer iplayer)
{
    if(iplayer != null) // does not use the overloaded == operator in the UnityEngine.Object class
    {
        Debug.Log("iplayer != null."); // this also gets printed
    }
}

And since the is operator can not be overridden, it has not been possible for Unity to make it return true for destroyed Objects.

Player player = GetComponent<Player>();
Destroy(player);
if(player is not null)
{
    Debug.Log("player is not null"); // this also gets printed
}

It doesn’t matter that much; since GameObject does not have any HasComponent method, using TryGetComponent like this makes sense.

However, in C# you can use an underscore (_) to discard a variable, which explicitly tells both the compiler and anybody reading your code, that it is not used. It is good practice to use this to make your intent clear.

if(gameObject.TryGetComponent<IArrowable>(out _)
    && !gameObject.TryGetComponent<IInteractable>(out _))