How can I provide a generic-access constructor?

I want to be able to “construct” scripts without referencing them directly. I’m referencing them via an interface, so they’ll all have the same functionality, but different names. The reason for this is that different objects have different purposes, so they’re checking for delegates and performing actions on their own.

Ex:

public class Blanket: IEquipable {
    public Blanket(Character wearer) {...}
}

public class Helmet: IEquipable {
    public Helmet(Character wearer) {...}
}

My characters are script (non-monobehavior) objects, so when they go to use them, it would be by accessing a List of all items they have equipped for this example. So, I would add them like so:

public void EquipItem(IEquipable item) {
    listOfEquippedItems.Add(new item(this));
}

Hopefully I explained the scenario properly! Not sure how to go about doing this.

I think what you’re trying to ask is you want to serialise plain C# types by an interface. That can be done with the SerializeReference attribute.

However Unity doesn’t have the inspector support to select types for these fields. So you’ll either need to implement it yourself, or get an addon like Odin Inspector.

Mind you, having all these plain C# classes feels like it’s going to be an architectural nightmare, with tons of boilerplate. Have you considered using scriptable objects?

I don’t really need to Serialize them, my issue is slightly different. I started with ScriptableObjects, but I couldn’t find a good way to implement what I wanted.

Basically, I have a turn-based system. Different objects have different effects that kick in during different actions. A helmet might raise your defense before taking a hit, while a blanket might restore health at the end of each turn. I broadcast delegates during significant events, and I wanted to create objects to track these events that I assign to each character. That’s why my constructors above had the wearer as an argument – I was going to assign a reference to the relevant events in the constructor.

There’s probably a better way to implement it, I just didn’t come up with it yet.

EDIT: I found this post on SO: c# - Abstract class with constructor, force inherited class to call it - Stack Overflow
I’m basically trying to do the opposite; I’m accessing the inheritor by base (passing Helmet with IEquippable helmet) and I want to call the constructor on the inheritor. Is this possible?

I think you should just define an Initialize method in your interface, and provide a factory pattern system to produce instances of them.

1 Like

If item is an interface (which in your example it is) then that’s just not a thing. The type is undefined and there is no logical way to derive it from the code. At some point somewhere you have to explicitly say what type you want to instantiate.

My question would be: What logic in your game decides which item has to be created? That’s the spot where you then need to call the item constructors, explicitly.

Side note: if you make your interfaces require the character as a reference then you have tightly coupled them to character. Try to flip that around. Give the item to the character which then can connect whatever event handles or delegates the item needs.

3 Likes

This is not valid C# code, you cannot new() a variable (item), you can only create types.
In your example the item is of type IEquipable, but to call the EquipItem method the item has to have a concrete type when it was created. For example:

IEquipable helmet= new Helmet();
EquipItem(helmet);

You have to choices:
Either in the EquipItem method you have an argument that you can use in a factory that creates the item you want with this as parameter in the constructor,
or you leave the EquipItem method as is and before you add the item it to the list you have a public IEquipable property of type Character which you set to this.

In you example the item parameter is already an instance with a type(IEquipable), that has been created somewhere else with a concrete type: either as a Blanket or a Helmet.

3 Likes

As others have already pointed out what you’re doing here just doesn’t make sense. I think one issue here is that you did not clearly explain what you want to do. One part of the issue is the usage of vague terminology like “script”. The term “script” does not really exist in C#. Within Unity the term specifically refers to a C# file that contains a MonoBehaviour derived class. This combination of file and class (which have to have the same name) is called a Script in Unity. Those can be attached to gameobjects.

Of course you don’t talk about “those” scripts since you have ordinary classes that are not derived from MonoBehaviour and you want to create them manually by using the constructor. In C# there’s the concept of types and objects (or instances of types). Those are two very different things. Variables or parameters of methods (which are also variables) do have a type and would need to contain an actual value / instance of that type. You can not directly pass a “type” in a variable.

C# has the concept of generic types and generic methods which allow to specify a “type argument”. However generic type arguments are not variables. Generic arguments allow the CLI to late bind an actual type at runtime and may produce specialized types or versions based on the used type. It is possible to create a generic method where you can specify a certain type and have the method create an instance of that type. For this the generic argument needs to have the “new” constraint which would require the type to provide a parameterless constructor. It does not work with a constructor that takes any parameters.

public void EquipItem<TItem>() where T : IEquipable, new()
{
    listOfEquippedItems.Add(new T());
}

This would be possible, though as I just mentioned, it only works with a parameterless constructor. Your interface may include some sort of Init method that could be called after it has been created. To call this method you would do
EquipItem<Helmet>();
However as I said, Helmet in this case is not and can not be a variable. This is just a generic method that can be used with different types. It’s like creating separate methods for each type, just that you don’t have to create them manually.

What you may have in mind is somehow pass a “type” to the method of the object that should be created and added. This is possible in C#. though not that way you want it to work. This is only possible by using the reflection system of C# / .NET. The CLI of .NET has a type system that includes a special type called “System.Type”. This class is used to “describe” any type within the CLI. This is part of the reflection system and every type has a System.Type object. You can get that object by using the “typeof” operator System.Type itemType = typeof(Helment); or when you have an instance of a type you can use the GetType() method that every type has. This includes even primitive types such as integers or strings.

var t1 = typeof(string);
var t2 = "Hello World!".GetType();

t1 and t2 would both contain the System.Type object that describes the System.String type.

With the System.Type object of a type you can do all sorts of things, including creating an instance of that type. This has to be done either through the System.Activator class, or by getting a ConstructorInfo instance of that class and invoke that. The Activator class simplifies the usage as it tries to find the “best” constructor that fits your parameter list.

Note that using reflection is kinda slow and you can easily mess things up. So type incompatibility issues will only show up at runtime when you try to execute it. You also need to cast the created instances to your desired type.

Unity itself uses reflection for adding components to gameobjects. The AddComponent method takes a System.Type object to specify which type should should be used to create a new instance that will be attached to a gameobject. The generic version of AddComponent is just a shortcut for

public T AddComponent<T>()
{
    return AddComponent(typeof(T)) as T;
}

So Unity instantiates the actual component on the native C++ side based on the passed System.Type object.

Maybe you’re looking for something like that as well? Though I would say it’s a bad design that an equipable object requires the wearer as an argument in the constructor. That means those objects can not exist unequipped since you can not create one without a wearer. It would make much more sense to have an Equip / Unequip method in that interface. Here you would pass the wearer as argument to the Equip method. Usually instances of equipable items may be used at other places as well, like placing them in an inventory or dropping it into the world or something like that. The “IEquipable” interface would only be the part that is relevant for an object that can be equipped. Though equipable items may have other uses as well.

3 Likes

Based on this code, it looks like you want to implement a prototype system. You are trying to make a new item to attach to the user rather than attach the one that was handed to the EquipItem method. You could do that with a Clone method

public interface IEquipable{
  Clone() IEquipable
}

public class Blanket : IEquipable {
  public IEquipable Clone() {
    return new Blanket()
  }
}

However, you still need to deal with constructor parameters. In your example, you only pass the object which is doing the equipping. If that’s always what you want to do, then you might be able to define an Interface for the Equipper and pass it to Clone.

public interface IEquipper{
  // ...
}

public interface IEquipable{
  Clone(IEquipper) IEquipable
}


public class Blanket : IEquipable {
  public IEquipable Clone(IEquipper e) {
    return new Blanket(e)
  }
}

However, all this seems pretty strange to me.

Based on this description, it actually sounds more like you want an Observer pattern. In that case, it may not make sense to pass the equipper into the equipment’s constructor. In an Observer pattern, typically only one object holds a reference to the other instead of both objects holding references to each other. In your use case, it’s likely you still need to interact with the observed component, so you would pass a delegate for getting other systems from the equipper at the time of the events.

public interface IEquipment{
  AttackInfo BeforeHit(AttackInfo attackInfo, IEquipper equipper)
  void AfterTurn(IEquipper equipper)
  // ... other important events in your game
}

public interface IEquipper{
  T GetComponent<T>()
}

public struct AttackInfo {
  public int Magnitude { get; set; }
  // ... other aspects of attacks in your game
}

public interface IHealthSystem {
  void Heal(int)
  void Damage(int)
}

public class Helmet : IEquipable {
  public AttackInfo BeforeHit(AttackInfo attackInfo, IEquipper equipper) {
    attackInfo.Magnitude = Math.Min(0, attackInfo.Magnitude - 5)
    return attackInfo
  }
}

public class Blanket : IEquipable {
  public void AfterTurn(IEquipper equipper) {
    var hpSystem = equipper.GetComponet<IHealth>()
    hpSystem.Heal(5)
  }
}

Apologies in advance for any syntax errors… I have not been coding in C# for a while.

2 Likes

This is pretty close to what I was looking for! I ended up finding that

Activator.CreateInstance(item.GetType())

had most of the functionality I needed.

The system I’ve designed doesn’t totally make sense, I agree, but it’s the only way I could figure out to incorporate my desired functionality into my existing system.

This is actually the technique @Bunny83 posted about: reflection. It is extremely flexible and powerful, but comes with some caveats that are important to know about.

First of all, it is slow; about 10 times slower than an alternative which does not use reflection. This is probably not a concern so long as you don’t use it a lot.

Secondly, and probably more importantly, it is not compatible on systems which do not support JIT compiling. I’m not up-to-speed on which systems that is exactly, but I expect it means your game will not be able to make use of things like the Burst compiler nor be compiled to run on mobile devices.

Others will be more knowledgeable about what specific limitations you are placing on your code by using this technique.

The Clone approach I suggested is quite different (and more verbose) in implementation and execution, despite having similar looking code.

1 Like