Casting MonoBehaviour to C# Interface works, but casting Component does not

In my test, I define a class (SubGO, Sub Game Object) that is both a MonoBehaviour and interface (IInputSrc4ACT). Then, in my testing scene, I let another class (BaseGO, Base Game Object) contain a reference to an instance of SubGO. In the Start() of BaseGO, I cast that SubGO reference to an interface reference, then, I try to access that interface reference. What I find is that interface reference can be correctly converted from MonoBehaviour or Behaviour, while casting from Component or Unity Object results null references. Why?

Here below is the code of BaseGO.cs.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IInputSrc4ACT //input interface for ACT game
{
    bool Jump();
}

public class BaseGO : MonoBehaviour
{
    public int val;

    public MonoBehaviour inputSrcRef;
    //public Behaviour inputSrcRef; also works.
    //public Component inputSrcRef; Component or Object not work!

    public IInputSrc4ACT inputSrc; //interface we plan to use

    public SubGO inputSrcRefConcrete; //not important in this test

    void Awake()
    {
        inputSrc = inputSrcRef as IInputSrc4ACT;
        //inputSrc = (IInputSrc4ACT) inputSrcRef; //as is better
    }

    void Start()
    {
        inputSrc.Jump();
        //If inputSrc is casted from MonoBehaviour or Behaviour, this works correctly.
        //If inputSrc is casted from Component or Object, this cause null-reference error.

        RestoreSubGo(inputSrc);

        inputSrcRefConcrete.Jump(); //not important in this test  
    }

    //when casting back, see if any data is lost
    void RestoreSubGo(IInputSrc4ACT interf)
    {
        SubGO subGo = interf as SubGO;
        Debug.Log("subGO-" + subGo.nameStr + "-" + subGo.id);
    }
}

Here below is the code of SubGO.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SubGO : MonoBehaviour, IInputSrc4ACT
{
    public int id;
    public string nameStr;

    public bool Jump()
    {
        Debug.Log(id + ": Jump implemented");
        return true;
    }
}

This picture below shows BaseGO instance in my testing scene.
Its InputSrcRef contains a reference to SubGO instance in the scene.

This picture below shows SubGO instance in my testing scene.

By rights this should work. I would try unsafe casting and see what error message is thrown. As is generally a bad idea as it fails silently.

When in BaseGo you have the code setup to be like this:

public Component inputSrcRef; Component or Object not work!

What is the actual object referencing?

What do you see here:
2943748--217904--BaseGO_Blargh.jpg

Note how in this image you see (SubGO), this is saying that it’s referencing the ‘SubGO’ component on the object ‘SubGO’.

Thing is, when you have the field typed generically like ‘Component’, really it just grabs the first component it finds.

It might be grabbing the ‘Transform’… does it say (Transform) there when you change the code? Or anything other than (SubGO)?

Transform’s don’t implement your interface.

What you may want to do is get the component by interface type like this:

void Awake()
{
    inputSrc = inputSrcRef.GetComponent<IInputSrc4ACT>();
}

Ensuring that if inputSrcRef is pointing at the wrong component, or at the GameObject itself, you get the actual script you need.

2 Likes

This right here. The only time you should use ‘as’ to cast is if you know that the cast might fail, and you’ve got code handling the null case. In your example, you should be getting a cast exception in Awake that’d tell you what you’re casting wrong, but you’re getting a nullref in Start instead.

1 Like

@lordofduct
You are exactly right. Thank you. :slight_smile:

Yes, when I use “public Component inputSrcRef”, and drag a SubGO instance in Unity editor onto inputSrcRef slot of BaseGO, that slot shows ‘Transform’. Now, I understand how the underlying mechanism works: it is trying to find the first type that can cast to the field type. I did some tests to verify this mechanism.

I also tried your snippet:

void Awake()
{
    inputSrc = inputSrcRef.GetComponent<IInputSrc4ACT>();
}

It works with “public Component inputSrcRef”.
@lordofduct @Kiwasi @Baste
Now I have another question for this:
I thought GetComponent can only get and return a Unity Component? Why it works on my own defined interface, IInputSrc4ACT?

It can only return a Component. But any Component that can be cast to your interface is still a Component. Polymorphism and all.

It basically looks like this under the hood.

public T GetComponent<T>(){
    foreach (Component component in allComponents){
        if (component is T) {
            return (T)component;
        }
    }
    return null;
}
2 Likes

Also worth mentioning that for a long time, this exact case didn’t work. IIRC there used to be a constraint on GetComponent so you couldn’t pass non-Component types to it. Thank god that’s changed.

2 Likes

Oh, I see. Thank you for explanation.