Chayanin
Chayanin

Reputation: 311

Is the invocation of a generic method allowed inside another generic method?

I'm making a RTS game with Unity. There're many types of resources in my game, such as, tree, farm. Each resource is a GameObject and has it own main script controlling it.

Ex. I want to harvest a tree, I call this.

gameObject.GetComponent<Tree>().Harvest();

If I want to harvest farm I call the same script but change "Tree" to "Farm" which is fine but code will be duplicated. So I abstract it by using generics method like this.

void Harvest<T>(){
    gameObject.GetComponent<T>().Harvest();
}

But the C# compiler won't let me do that. I want to know is it possible to define generics method that use generics method inside? If not, Is there any way to abstract my code like this? Thank you.

Error message:

'T' does not contain a definition for 'Harvest' and no extension method 'Harvest' accepting a first argument of type 'T' could be found (are you missing a using directive or an assembly reference?) [Assembly-CSharp]

Upvotes: 3

Views: 71

Answers (2)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112392

The problem is that in ...

void Harvest<T>(){
    gameObject.GetComponent<T>().Harvest();
}

... the C# compiler does not know of which concrete type T will be. Therefore, it cannot know that there will be a method Harvest available, nor does it know its exact declaration (does it return void or bool or something else? Does it have optional arguments?). Because C# is a strongly typed language, this must be known at compile time. This gives you the certainty that everything will go well at runtime.

The solution is to give the compiler a hint by specifying a generic type constraint. To do this you must declare an interface and let the components with a Harvest method implement it.

public interface IHarvestable
{
    void Harvest();
}

Specify the constraint with:

void Harvest<T>() where T : IHarvestable 
{
    gameObject.GetComponent<T>().Harvest();
}

In other situations where you are in control of the base class, you can also declare the required methods in the base class (possibly as abstract) and specify the base class in the generic type constraint instead of an interface.

Upvotes: 5

Nick Louloudakis
Nick Louloudakis

Reputation: 6005

Define an interface for all objects that use Harvest(), then define that T extends that interface:

public interface IHarvestable
{
    void Harvest();
}

// In your class:
void Harvest<T>() where T: IHarvestable
{
    gameObject.GetComponent<T>().Harvest();
}

BAD alternative (mentioned just as a "hacky" addition to the answer because C# supports this - do NOT use it in practice): If you want to skip -time checking you can use dynamic:

dynamic harvestable = gameObject.GetComponent<T>();
harvestable.Harvest();

Note this is a bad practice, leading to method call resolving at runtime, leading to performance drawbacks and making your code much more error prone. For instance, usage of method from a T type instance which does not implement Harvest() will be allowed by the compiler, leading to a runtime error.

Upvotes: 2

Related Questions