Is the 'record' function fully implemented?

It sounds like you tried defining a property named Value in both the derived and base types.

Something like this maybe:

public class SomeClass
{
    public object Value { get; }
}

public class SomeClass<T> : SomeClass
{
    [SerializeField] private MyClass[] _value;

    public T Value => _value;
}

If you do this, and the member in the base type isn’t virtual or abstract, and the member the derived type an override, then the compiler will warn you that the derived type hides the field in the base type. More information about member hiding here.

You rarely want to do member hiding, but in the very particular case, where you want to replace a property of type object with a property of type T returning the same value, member hiding is a perfectly valid option (I often do this myself).

So you could do something like this:

public abstract class SomeClass
{
    public object Value => GetValue();
    
    protected abstract object GetValue();
}

public class SomeClass<T> : SomeClass
{
    [SerializeField] private T _value;

    public new T Value => _value;
    
    protected override object GetValue() => _value;
}

Thank you. So is there no way to just add a value that is on all instances of MyClass that you can just get by going myClass.value? The way you can with nullable T values like:

Vector3? test;
test.Value

Basically, I want to be able to just standardise my code in a way that’s friendly to modders. For example by letting them get the int ID off of anything with an id by going thing.ID. I feel like this is possible, but that I’m just not getting something really vital about this.

If you want all of these objects that modders work with to always have an id, and for it to always be of type int, then you can just stick a property of type int into a base class:

public abstract class Entity
{
	public int Id { get; }

	public Entity(int id) => Id = id;
}

If you want only some of them to have an id of type int, then you can create an interface which only those objects that have an id will implement:

public interface IIdentifiable
{
	public int Id { get; }
}

public class Entity : BaseObject, IIdentifiable
{
	public int Id { get; }

	public Entity(int id) => Id = id;
}

Then modders could check if an object has an Id and acquire it like this:

if(baseObject is IIdentifiable identifiable)
{
	return identifiable.Id;
}

If the type of the Id can wary, then you can use generics like Nullable{T} does:

public abstract class Entity<TId>
{
	public TId Id { get; }

	public Entity(TId id) => Id = id;
}

or

public interface IIdentifiable<TId>
{
	public TId Id { get; }
}

public class Entity : BaseObject, IIdentifiable<int>
{
	public int Id { get; }

	public Entity(int id) => Id = id;
}
1 Like

Thanks a lot. I think I understand more. Unfortunately, using the second method, it is telling me the ID is read only. Is there any way to fix this? If this is all read only it’s probably not worth it.

Also, is there a way to make this work with arrays? Could I, for example, say:

[SerializeField] private MyClass[] _myClass { get; } = new MyClass[10];
public Entity(MyClass[] _myClass) => myClass = MyClass[] _myClass;

I have tried this in the base class and it forces me to put this in the code of inherited classes:

public NeClass(MyClass[] _myClass) : base(myClass)
{
}

To make the Id property read-write, simply add an a set accessor.

Yes, it works with arrays as well.

If you have a base class like this:

public abstract class Entity<TId>
{
	public TId Id { get; set; }
}

Then you can create a derived type like this:

public class class MultiIntIdEntity : Entity<int[]> { }

When you create an instance of a class using the new operator in code, one of the constructors of the class needs to be executed.

If you don’t define any constructors in a class, a parameterless constructor is generated automatically, which makes it possible to just do new Entity() even if no constructor was defined in the Entity class.

If you define a constructor with parameters in the Entity class, then a parameterless constructor no longer gets automatically generated. So now the code new Entity() will no longer compile, and you have to execute the constructor which accepts arguments instead, like new Entity(array).

Similarly, if you don’t have a public parameterless constructor defined in the base class, then you have to define constructors in all derived types, and specify what arguments to pass to the constructor in the base class. E.g.:

public Neclass() : base(new MyClass[0]) { }

or

public Neclass(MyClass[] array) : base(array) { }

If you don’t define any constructor in your base class, but add a read-write Id property instead, then you’ll be able to create instances using just new NeClass(), and providing an Id will be optional. If nothing is assigned to the property after initialization, then it will have the default value of the type (0 for int, null for arrays etc.).

1 Like

If you don’t define any constructor in your base class, but add a read-write Id property instead, then you’ll be able to create instances using just new NeClass() , and providing an Id will be optional.

Could you explain how to do that? Sorry XP Just not sure what you mean as I already have { get; set; }

image

Also having a bit of issue adding one of those contractors to the class. Says ‘MonoBehaviour’ does not contain a constructor that takes 1 arguments’. So I’m assuming it has to be a pure class not derived from anything?

A base class for components shouldn’t have a constructor.

public abstract class Entity<TId> : MonoBehaviour
{
	public TId Id { get; set; }
}

If you want to expose the id in the inspector, you can do this:

public abstract class Entity<TId> : MonoBehaviour
{
	[SerializeField]
	private TId id;

	public TId Id
	{
		get => id:
		set => id = value;
	} 
}

Alright. Not sure I fully understand, but is there a way to stop myself from having to put this into the derived class? If not, I’ll just have to remove all this stuff and use an abstract method.

public NeClass(MyClass[] _myClass) : base(myClass)
{
}

You shouldn’t use constructors with parameters with MonoBehaviour-derived classes. Unity creates the components for you, so you can’t directly pass any arguments to their constructors. So in this case an abstract method would be better.

Okay. Thanks for your help but I think I’m just going to have to rip out all this code and start again. It’s not going to do what I want it to, which is just to store a generic value that can be one of two different classes.

It’s entirely possible, you just need to have concrete monobehaviour classes to work with each respective generic type you want to use it for.

It’s fairly straight forward:

public abstract class SomeClass { ... }

public sealed class DerivedClassA : SomeClass { ... }

public sealed class DerivedClassB : SomeClass { ... }

public abstract class GenericBase<T> : MonoBehaviour where T : SomeClass, new()
{
	[SerializeField]
	private T _someClass = new();
	
	public T SomeClass => _someClass;
}

public sealed class GenericDerivedA : GenericBase<DerivedClassA> { ... }

public sealed class GenericDerivedB : GenericBase<DerivedClassB> { ... }

Edit: Obviously each of the derived monobehaviour types need to be in their own script asset.

1 Like

Thank you. I’ll try that later, but I’ve just realised you can literally do this:

BaseClass base = new DerivedClass();
if (base is DerivedClass derived) { derived.intContainedInDerrivedClass = 5; }