Passing a class type to C# method

Godot Version

4.3

Question

I have this method that I’ve been using in gdscript:

func has_component(component_type: Object) -> bool:
	for c in components:
		if is_instance_of(c, component_type):
			return true
	return false

I like this because I can pass a class as a parameter rather than an instance of that class, so I can easily check if my entity already has a component of that type. For example:

entity.has_component(MovementComponent)

I’m new to using C# with Godot, and I’m trying to implement this method. My issue is that I’m not sure what parameter type in C# is suppose to represent classes in gdscript.

public void HasComponent(??? componentType)

Any help would be greatly appreciated. I’m also open to other strategies, though I’d like to avoid type checking via String.
Thanks!

C# Method Parameters

Type Object would work. But if you only check other nodes, then use the Node class instead.

What is the reason you want to use c# instead of gdscript?

The approach you would want to use in C# is different since the syntax is different. You won’t be able to directly convert your GDScript code to C#.

I primarily use C# for my projects and have implemented methods similar to the one you’re seeking to convert (see end of post).

Here is how I would implement the functionality of has_component in C#:

	public bool HasComponent<T>(this Node n, bool recursive = false) where T : Node
	{
		for (int i = 0; i < n.GetChildCount(); i++)
		{
			if (n.GetChild(i) is T)
			{
				return true;
			}
			else
			{
				if (recursive)
				{
					if (n.GetChild(i).HasComponent(recursive))
					{
						return true;
					}
				}
			}
		}

		return false;
	}

The primary feature of the C# language being used here is a generic type parameter (see MSDN). Using this parameter, you can run the code within the function for a specified type which, for most cases, should be named T.

The type-testing is done with the is keyword and the aforementioned type parameter, T. Recursion is used to allow the function to detect all descendants of a particular node. This is enabled through the recursive parameter which, by default, is false.

The first parameter that uses the this keyword is a direct reference to the node on which this method is called. This makes invocation of the method identical to how you’re currently invoking has_component(). For example: entity.HasComponent<MovementComponent>().


I hope that helps. Let me know if you have any questions.

TryGetNode()
	/// <summary>
	/// Attempt to find a node of type, T, in this node's children.
	/// </summary>
	/// <typeparam name="T">Node type.</typeparam>
	/// <param name="node">The found node.</param>
	/// <param name="recursive">If enabled, performs this operation recursively to perform a depth-first search.<br />NOTE: Should probably be breadth-first.</param>
	/// <returns>Whether a node was found.</returns>
	public static bool TryGetNode<T>(this Node n, out T node, bool recursive = false) where T : Node
	{
		for (int i = 0; i < n.GetChildCount(); i++)
		{
			// GD.Print($"Checking {n.GetChild(i).Name}...");
			if (n.GetChild(i) is T)
			{
				// GD.Print($"Found the {typeof(T)} node!");
				node = (T)n.GetChild(i);
				return true;
			}
			else
			{
				if (recursive)
				{
					if (n.GetChild(i).TryGetNode(out T recurseResult, recursive))
					{
						node = recurseResult;
						return true;
					}
				}
			}
		}

		node = null;
		return false;
	}
GetAllNodes()
	/// <summary>
	/// Finds all nodes of type T amongst this node's children.
	/// </summary>
	/// <typeparam name="T">The target node type.</typeparam>
	/// <param name="recursive">If enabled, performs this operation recursively to perform a depth-first search.<br />NOTE: Should probably be breadth-first.</param>
	/// <returns>All children of type T.</returns>
	public static T[] GetAllNodes<T>(this Node n, bool recursive = false) where T : Node
	{
		List<T> nodes = new();
		for (int i = 0; i < n.GetChildCount(); i++)
		{
			if (n.GetChild(i) is T nt)
			{
				nodes.Add(nt);
			}

			if (recursive)
			{
				T[] childResult = n.GetChild(i).GetAllNodes<T>(recursive);
				nodes.AddRange(childResult);
			}
		}

		return nodes.ToArray();
	}

Thanks a lot for the reply.
Would these methods work if they were called from GDScript?
What I’ve deduced so far is that Node in GDScript is different than Node in c# and cannot be compared with is?
Node in GDScript is apparently a GDScriptNativeClass (which doesn’t seem to have any documentation), and Node in c# is a Type(?).
Also, my understanding is that Godot wouldn’t be able to call a generic method?

Also, now that I’m testing it I don’t think I would have thought to use generics to check the type any time soon, so thank you for that :slight_smile:

I’m mostly here because not having access to interfaces was driving me crazy. That and I have plans for some pretty performance heavy calculations later on.

According to this docs page, yes. However, it is also stated that Godot will “do it’s best” which makes me unsure of whether extension methods or methods with generic type parameters are supported. You will have to test that.

I’m not entirely sure of what you’re trying to say here. C# and GDScript are separate languages but they both, ultimately, support the same Variant type(s). A Node is a class in both languages.

Again, I’m not too sure on that either. You will have to do some testing.

They both support Variants yes, but in all my experimenting I haven’t been able to pass the class (not an instance) ‘Node’ from gdscript, compare it to ‘Node’ in c#, and have it not return false.

Anyway, I’m finding ways to structure my code without needed to rely on my old way of doing things so hopefully it’s not a big deal.
Thanks again for the help, that generics trick saved me.

I believe you can use out to have bool and class
like in Unity TryGetComponent

public static class ComponentHelper
{
    public static bool TryGetComponent<T>(Array components, out T component) where T : class
    {
        foreach (var c in components)
        {
            if (c is T typedComponent)
            {
                component = typedComponent;
                return true;
            }
        }
        component = null;
        return false;
    }
}