Is the 'record' function fully implemented?

Hello, I’m trying to use ‘record’ to make generic classes where certain parts are interchangeable. However it doesn’t seem to be working. For example, I created this basic test:

public abstract record StorageModule;
public record Store(int value) : StorageModule;

This is generic code I got off ChatGPT. But in this test code, the ‘value’ part comes up with an error:

CS0518: Predefined type ‘System.Runtime.CompilerServices.IsExternalInit’ is not defined or imported

Is there a way to get this working? Or some other way to create classes with generic parts? At the moment I’m stuck using a bunch of classes that are almost identical, except for one line, which then each require custom code and custom lists or dictionaries to store them. Or for me to have a bunch of redundant fields.

Unity only supports up to C# version 9.0.

Anything beyond that is off limits for use in Unity.

Covered comprehensively in the docs: Unity - Manual: C# compiler

Pretty sure you can use regular inheritance for this, which may include using objects (rather than primitive value) for swapping out the behaviour of various components.

Thank you for that. I was told I could do this with C# 9.

Could you give me an example of a class using objects? Or some other generic storage field that is compatible with Dictionaries and normal return methods when it is wrapped in a class? I’m having trouble making it work.

To use c# 9 records and init add this script to your project IsExternalInit.cs:

namespace System.Runtime.CompilerServices;
{
    public class IsExternalInit
    {
    }
}

Edit: Woops, wrong reply

1 Like

Oh? Really? Just that one thing, nothing else? Are you sure? Wouldn’t Unity add that if it was that easy?

Interestingly enough, but yes. It’s seems like they are working that way. And afaik they are officially supported too, partially according to different sources, but supported.

Honestly just sounds like you need a non-generic base class with a generic derived type.

1 Like

In code form now I’m in front of a computer:

public abstract class SomeClass
{
	
}

[System.Serializable]
public class SomeClass<T> : SomeClass
{
	[SerializeField]
	private T _value;
	
	public T Value => _value;
}
1 Like

Records are supported out of the box - but you can’t use the primary constructors or record structs without the IsExternalInit trick. But even without that, you can get all the auto-generated goodies (equality operator overrides with value-semantics, ToString override).

It’s part of newer .NET releases, they may not want to backport that type when it’s not specified in the frameworks we get. Related example for when they were on C# 8:

With that said, their option of a union of .NET Framework 4.8 and .NET Standard 2.1 is a strange thing that exists already, so they’ve already broken some rules…

2 Likes

This is turning out to be odd as heck.

Honestly just sounds like you need a non-generic base class with a generic derived type.
Yes! That’s basically all I need. Nothing fancy. I’ll try your trick, thank you.

Ah. So can I not use T with normal dictionaries without stipulating what the T is? My code won’t break if multiple types are stored as T because it checks it against a second key code to find out what the T is.

Kinda really need a totally generic slot to put a custom class worth of data in to make this work smoothly tbh

The point is to use the non-generic base class for the dictionary. Then you can store derived types through polymorphism, even if they’re different derived types with different generic implementations.

private readonly Dictionary<int, SomeClass> _classTable = new();

var someIntClass = new SomeClass<int>();
_classTable.Add(someInt, someIntClass);
1 Like

Thanks a lot. This is kinda quirky how this works! Some tweaking and I have some test code that seems to be valid. Could you tell me if I can do anything with that abstract class, or if I should just put everything in the derived class?

Like I said in the other thread you made, you can still put whatever you need into the base most class. It doesn’t necessarily have to be abstract, either; that was just part of the example. If you need methods, fields, properties, etc in the base most implementation, then put it there. Likely the stuff that doesn’t depend on the generic typing.

It’s a common pattern I’ve been doing while coding a drawer system for the crafting UI in my current project.

        [Serializable]
	public abstract class RecipeOutputDrawer : Drawer
	{
		public abstract RecipeOutputElement GetDrawerElement(RecipeOutputProperty outputProperty);

		public virtual RecipeImplInfoContent GetOutputInfoContent(RecipeOutputProperty outputProperty)
		{
			return RecipeImplInfoContent.GetNoneContent();
		}
	}

	[Serializable]
	public abstract class RecipeOutputDrawer<T> : RecipeOutputDrawer where T : class, IRecipeOutputImpl
	{
		#region Properties

		public sealed override Type DrawerForType => typeof(T);

		#endregion
	}

Thanks a lot! You’ve really helped me here!

Is there a way to change the size of this array at runtime, or am I stuck with whatever number of slots I have specified? It seems the value is read-only, though I can still change the attached data.

[SerializeField]private MyClass[] _value = MyClass[5]; public MyClass[] Value => _value;

Arrays can’t be resized. Use a List<T> instead.

2 Likes

Ah. Okay. It seems to be coming up with a length of zero even if I set it before runtime. Are they just not usable? If so, I can make a list, but it would muck up a lot of my code there. It’s all dependent on the position an object is in an array.

Would be easier if I could just specify the maximum size the array can be in code. But that doesn’t seem to work.

With a non-serialized field, you can set the length of the array using a field initializer:

private MyClass[] _value = { new(), new(), new() };

With a serialized field, you instead set the length of the array, and its contents, using the Inspector.

If you change the field initializer of a serialized array, it will only affect the default value in newly added components. For any components that already exist in your scenes and prefabs, their serialized values will override whatever was assigned using the field initializer.

[SerializeField] private MyClass[] _value; // <- doesn't even need a field initializer

Arrays have a fixed length, so you can’t resize an array object once it has been created.

The array object that is assigned to the _value field will always have a length of zero:

_value = new MyClass[0];

However, you can create a completely new array object with a different length, copy all items from the old array to the new array, and assign the new array into the same variable.

The Array.Resize method can be used to perform all these steps for you:

void AddToArray(MyClass item)
{
    Array.Resize(ref _value, _value.Length + 1);
    _value[_value.Length - 1] = item;
}

Side Note:
A little bit of “garbage” is generated every time you use Array.Resize, because the old array in the variable gets discarded, and the garbage collector needs to do a little bit of work to release it from memory. This is nothing to worry about at all in most cases, but if you do it every frame for example, then switching to use a List could be worth considering.

Side Note #2:
Even disregarding micro-optimizations, it’s in general considered good practice to prefer using an array when the size of the collection remains unchanged after initialization, and a List whenever the size can get changed after initialization. This way when somebody reads your code, the collection choice gives the reader a good hint about what to expect from the code.

So while using Array.Resize might make sense in your particular situation, to help avoid having to rewrite a lot of code, you should aim to use List.Add more often than that in the future.

1 Like

Okay, thank you. I am having an issue where c# is complaining that the _value is hidden by me adding the ‘Value’ field to a class that inherits from MyClass. However, removing it causes an error. I feel that I’m doing something wrong. Should I be overriding or similar? It does seem that the _value field is attaching to the object because it shows up in Inspector when I use [SerializeField].

I am assuming I need both in order to get the Value field by going myClass.Value?