I’m creating a modding framework for a game that allows you to make a temporary change to a global object. For example, if you want to change the properties of an object only for a certain save that won’t persist if you load another save during the same session.
Ref fields sound appropriate for this, and although I need to store the changes made in some sort of collection, using ref structs is fine because I can store a delegate that creates the struct instead.
The game is made in Unity, so it needs to be compatible with .NET Framework. This requires me to do sketchy things with .NET Standard and an old version of CommunityToolkit.HighPerformance, but the problem I have would be present even with the proper ref fields from C# 11.
The problem I have is I can’t figure out how to check if the reference is actually to a field in the object. I need to know the object that contains the field because I need to treat things differently if changes are made to the same field of the same object. The users of the framework need to be able to change any type of global object (or at least one type that has a BUNCH of subclasses), so I can’t reason about the fields that would be present in such an object.
If I could get a pointer to the start and end of the object, I could use Unsafe.AsPointer on the field reference and check if it’s between the start and end pointers. I’ve figured out how to get a pointer to the start of an object by getting a pointer to one of its references, then turning the dereferenced pointer into a new pointer (I think this is safe because I use this pointer ONLY in the method it’s created in). However, I can’t figure out how to get the end. I tried using Unsafe.SizeOf and adding that to the start pointer, but Unsafe.SizeOf seems to just return the size of a reference when used with classes.
This is what I have right now:
public unsafe TemporaryChangeField(T fieldOwner, ref U changedField)
{
long* fieldOwnerPtr = (long*)Unsafe.AsPointer(ref fieldOwner);
byte* fieldOwnerStart = (byte*)*fieldOwnerPointer;
byte* fieldOwnerEnd = fieldOwnerStart + Unsafe.SizeOf<T>();
byte* changedFieldPtr = (byte*)Unsafe.AsPointer(ref changedField);
Console.WriteLine((long)fieldOwnerStart);
Console.WriteLine((long)fieldOwnerEnd);
Console.WriteLine((long)changedFieldPtr);
if (!(changedFieldPtr >= fieldOwnerStart && changedFieldPtr <= fieldOwnerEnd))
throw new ArgumentException("Field specified is not contained in the specified field owner", nameof(changedField));
this.fieldOwner = fieldOwner;
this.changedField = new Ref<U>(fieldOwner, ref changedField);
}
This is the main constructor for my ref struct. T is the type of the object with the field being changed, and U is the type of the field. My current code would work if I could figure out how to get the actual size of the object, instead of just the size of a pointer.
5