This package contains the attributes [PolyStruct] and [GenerateInterfaceDelegates], along with their respective source generators.
The goal is to allow for pseudo-polymorphism while avoiding boxing and keeping the benefits of structs,
such as preventing heap allocations and preserving contiguous memory layout when used in arrays.
While this package can theoretically be used as is, it is not production ready and should be considered more of a reference or showcase, if anything.
This following code reflects the example in the Unity project.
[PolyStruct]
public interface IExample
{
public void Print();
}
public struct ExampleA : IExample
{
public void Print()
{
Debug.Log("ExampleA!");
}
}
public struct ExampleB : IExample
{
public void Print()
{
Debug.Log("ExampleB!");
}
}
public partial struct ExampleC : IExample
{
[GenerateInterfaceDelegates] // The struct must be partial for this attribute to take effect.
private ExampleA _base;
}
public class ExampleUsage : MonoBehaviour
{
private void Start()
{
Example a = new ExampleA();
a.Print(); // Prints "ExampleA!"
Example b = new ExampleB();
b.Print(); // Prints "ExampleB!"
Example c = new ExampleC();
c.Print(); // Prints "ExampleA!"
}
}The [PolyStruct] attribute tells the source generator to create a sort of union type, which can contain all other structs implementing the IExample interface.
This union struct implements the interface as well and delegates the methods to the currently contained 'instance'. Additionally the type provides some utility members for casting, etc.
// For [PolyStruct]:
[StructLayout(LayoutKind.Explicit)]
public partial struct Example : IExample
{
public enum Type
{
Uninitialized = 0,
Empty = 1,
ExampleA,
ExampleB,
ExampleC,
}
[FieldOffset(0)]
private Type _type;
[FieldOffset(8)]
private global::ExampleA _exampleA;
[FieldOffset(8)]
private global::ExampleB _exampleB;
[FieldOffset(8)]
private global::ExampleC _exampleC;
public readonly Type CurrentType => _type;
public readonly IExample Unwrapped
{
get
{
return _type switch
{
Type.Uninitialized => throw new InvalidOperationException("Struct has not been initialized."),
Type.Empty => null,
Type.ExampleA => _exampleA,
Type.ExampleB => _exampleB,
Type.ExampleC => _exampleC,
_ => throw new ArgumentOutOfRangeException(),
};
}
}
public static readonly Example Empty = new() { _type = Type.Empty };
public Example(global::ExampleA exampleA) : this()
{
_type = Type.ExampleA;
_exampleA = exampleA;
}
public Example(global::ExampleB exampleB) : this()
{
_type = Type.ExampleB;
_exampleB = exampleB;
}
public Example(global::ExampleC exampleC) : this()
{
_type = Type.ExampleC;
_exampleC = exampleC;
}
public void Print()
{
switch (_type)
{
case Type.Uninitialized:
throw new InvalidOperationException("Struct has not been initialized.");
case Type.Empty:
break;
case Type.ExampleA:
_exampleA.Print();
break;
case Type.ExampleB:
_exampleB.Print();
break;
case Type.ExampleC:
_exampleC.Print();
break;
default:
throw new ArgumentOutOfRangeException();
}
}
public readonly bool TypeEquals(in Example other) => _type == other._type;
public static Type TypeFor(in global::ExampleA exampleA) => Type.ExampleA;
public static Type TypeFor(in global::ExampleB exampleB) => Type.ExampleB;
public static Type TypeFor(in global::ExampleC exampleC) => Type.ExampleC;
public static string GetSizeInformation()
{
var sb = new global::System.Text.StringBuilder();
sb.Append("Example = ").AppendLine(Marshal.SizeOf<Example>().ToString());
sb.Append("ExampleA = ").AppendLine(Marshal.SizeOf<global::ExampleA>().ToString());
sb.Append("ExampleB = ").AppendLine(Marshal.SizeOf<global::ExampleB>().ToString());
sb.Append("ExampleC = ").AppendLine(Marshal.SizeOf<global::ExampleC>().ToString());
return sb.ToString();
}
public static implicit operator Example(global::ExampleA exampleA) => new(exampleA);
public static explicit operator global::ExampleA(Example example) => example._type == Type.ExampleA ? example._exampleA : throw new InvalidCastException();
public static implicit operator Example(global::ExampleB exampleB) => new(exampleB);
public static explicit operator global::ExampleB(Example example) => example._type == Type.ExampleB ? example._exampleB : throw new InvalidCastException();
public static implicit operator Example(global::ExampleC exampleC) => new(exampleC);
public static explicit operator global::ExampleC(Example example) => example._type == Type.ExampleC ? example._exampleC : throw new InvalidCastException();
}The [GenerateInterfaceDelegates] attribute can be usefull when trying emulate inheritance with these structs.
You can use this source generator to automatically implement members of the declared interface(s).
If annotated member has members matching their name and signature, the calls are delegated accordingly.
// For [GenerateInterfaceDelegates]:
public partial struct ExampleC
{
public void Print() => _base.Print();
}