0
\$\begingroup\$

I'm trying to use C# generics on a method in order to send a Vector3 (position) as a parameter. I can print the value correctly but can't store it on a local variable. I also can't access its x, y or z attributes separately.

I'm implementing a state machine. My intention is to use generics for allowing sending different parameters to the different states. The value will be sent to an "Enter" method which will be called once on each state every time that state is set to active or current. So for example: the player will be on the DefaultState->something happens->change state to HeroTeleportingState while sending it it's new position

This is my Enter method on the HeroTeleportingState class:

void IState.Enter<T>(T newPosition)
{
    //The following line prints the correct value
    Debug.Log("move this hero to " + newPosition);

    //The following lines don't work
    //pos = newPosition
    //pos.x = newPosition.x;
}

The method call is just something like: currentlyRunningState.Enter(Vector3.one);

pos = newPosition gives me the following error on Visual studio (I'm paraphrasing cause my visual studio is in spanish):

Cannot implicitly convert type 'T' to 'UnityEngine.Vector3'

And pos.x = newPosition.x gives me something like:

'T' does not contain a definition for 'x' and no extension method 'x' accepting a first argument of type 'T' could be found


EDIT: IState interface:

public interface IState
{
    void Enter<T>(T value);

    void Execute();

    void Exit();
}
\$\endgroup\$
  • \$\begingroup\$ You need to either introduce an explicit cast to Vector3 (including an instanceof check) or you need to declare T as extending Vector3. You can't just say "I want a thing" and assign that thing to a Vector3 property. What if you passed in a GameObject to this function? Or a string? An integer? A FileWriter? \$\endgroup\$ – Draco18s Aug 30 '17 at 15:04
  • \$\begingroup\$ @Draco18s well, this position = (Vector3)newPosition; throws the error Cannot convert type 'T' to 'UnityEngine.Vector3'. Also, I've never heard of instanceof checks, I'm trying to read about it but can't really understand. \$\endgroup\$ – Adocad Aug 30 '17 at 15:34
  • \$\begingroup\$ Can you show us the definition of the IState interface? \$\endgroup\$ – DMGregory Aug 30 '17 at 15:42
  • \$\begingroup\$ Sure, I edited the question \$\endgroup\$ – Adocad Aug 30 '17 at 15:47
4
\$\begingroup\$

Generics in C# don't work this way. It looks like you're using them the way you'd use templates in C++, but there are notable differences between C++ templates and C# generics. The biggest is that generics are instantiated at runtime.

That means that if you are going to use methods or properties of a generic type within a generic method, you need to tell the compiler what interfaces provide those methods and properties first. You do this with constraints. You can constrain on interfaces, base (reference) types, and a few other things not entirely relevant to your issue here.

Unity's Vector3 is a value type, and so cannot be used as a base type constraint. Even if it could, the practical upshot of doing that is to effectively make you "generic" method equivalent to the non-generic version, void IState.Enter(Vector3 newPosition).

You can't directly do what you're trying to do at the low level here. But you can still accomplish the high level goal:

My intention is to use generics for allowing sending different parameters to the different states. The value will be sent to an "Enter" method which will be called once at the beginning of every new state.

There are two ways, offhand, you might want to go about this. The simple solution is to write Enter as: void Enter(Dictionary<string, object> parameters), using a generic dictionary mapping from parameters names to parameter values, and letting each state subclass that implements Enter "know" which named keys to extract and how to cast them from object to do what is needed.

The second way is to write Enter as void Enter(StateEntryParameters parameters) where StateEntryParameters is a base class containing the appropriate parameters for the state. Enter implementations in each state subclass will still need to cast parameters to the correct subclass they expect. For example, the parameters for a state that takes a vector for a new position might look like:

class InitialStateParameters : StateEntryParameters {
    public Vector3 NewPosition;
}

You could also use an interface instead of a base class, since StateEntryParameters isn't (at least in this example) actually adding any value. Note that this approach also allows you to use generics with constraints on StateEntryParameters, but that's still effectively pointless.

A third option (and honestly, the one I'd prefer, but which may not work for you in the larger context of your design) is to simply pass the parameters you need to each state subclass in it's own constructor, and don't actually do anything with them in those constructors except store them for later, when you eventually call Enter(). This requires that you know what the parameters to each state will be when you create it, and so may not fit into your larger design, but it keeps the IState interface much simpler; Enter can be just as boring of a function as Execute() is.

\$\endgroup\$
1
\$\begingroup\$

Currently T can be anything, you don't want that. Instead, try making T extend Vector3:

void IState.Enter<T>(T newPosition) where T : Vector3

This way you can only pass something to this method, if it extends Vector3

\$\endgroup\$
  • \$\begingroup\$ Thanks, but I get an error on "where" when I add that. It has something to do with the interface I'm implementing (HeroTeleportingState implements IState). The error is something like constraints for validation and explicit interface implementation methods are inherited from base method, they cannot be specified directly \$\endgroup\$ – Adocad Aug 30 '17 at 15:28
  • \$\begingroup\$ @somezombie First of all, you should learn C# before starting Unity if you didn't do it before. You need to make the base interface/class have the same limitations on T as the implementations, aka. put where T : Vector4 after the method in the class it's defined in \$\endgroup\$ – Bálint Aug 30 '17 at 15:32
  • \$\begingroup\$ If I make the base interface (IState) have the same limitations on T, doesn't that defeat the purpose of generics? what if instead of Vector 3 I want to send an int? (on another state class) \$\endgroup\$ – Adocad Aug 30 '17 at 15:45
  • \$\begingroup\$ I added the definition of the IState interface to the question \$\endgroup\$ – Adocad Aug 30 '17 at 15:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.