-
-
Notifications
You must be signed in to change notification settings - Fork 51
Defining Immutable Objects
FlatSharp does its best to support creating immutable objects, or immutable properties within objects. Immutability is a useful programming practice in some scenarios, so FlatSharp does its best to help.
Most deserialized items in FlatSharp are immutable. The exceptions are objects deserialized using the GreedyMutable
(the default) or VectorCacheMutable
options. All other options will throw a NotMutableException
when trying to modify a deserialized object.
FlatSharp also supports defining immutable tables and structs, using a few different patterns:
If you're using C# 9, FlatSharp versions 5 and above support Init Only properties declared with the following patterns. This is the preferred way to make properties immutable in C# 9.
// FBS annotation: fs_setter:"Init"
public virtual string MyProperty { get; init; }
// FBS annotation: fs_setter:"ProtectedInit"
public virtual string MyProperty { get; protected init; }
// FBS annotation: fs_setter:"ProtectedInternalInit"
public virtual string MyProperty { get; protected internal init; }
// FBS annotation: fs_setter:"Init", fs_nonVirtual:"true"
public string MyProperty { get; init; }
// FBS annotation: fs_setter:"ProtectedInit", fs_nonVirtual:"true"
public string MyProperty { get; protected init; }
// FBS annotation: fs_setter:"ProtectedInternalInit", fs_nonVirtual:"true"
public string MyProperty { get; protected internal init; }
If using C# 8, FlatSharp supports non-public setters as well:
// FBS annotation: fs_setter:"None"
public virtual string MyProperty { get; }
// FBS annotation: fs_setter:"Protected"
public virtual string MyProperty { get; protected set; }
// FBS annotation: fs_setter:"ProtectedInternal"
public virtual string MyProperty { get; protected internal set; }
// FBS annotation: fs_setter:"Protected", fs_nonVirtual:"true"
public string MyProperty { get; protected set; }
// FBS annotation: fs_setter:"ProtectedInternal", fs_nonVirtual:"true"
public string MyProperty { get; protected internal set; }
Beginning in version 5, FlatSharp does not require types to expose default constructors. This behavior is useful if you wish to define your own constructors so that objects are never instantiated the wrong way. FlatSharp will be happy with any of the following constructors on your objects:
[FlatBufferTable]
public class MyClass
{
// FlatSharp will use this constructor if it is defined. It must be public, protected, or protected internal.
// This constructor is auto-generated when using FBS files.
protected MyClass(FlatBufferDeserializationContext context) { }
// FlatSharp will use this constructor if it is defined and there is no constructor accepting
// FlatBufferDeserializationContext items. This constructor is auto-generated when using FBS files
// and the 'fs_defaultCtor' metadata attribute is not equal to 'None'.
public MyClass() { }
}
This example shows an immutable table and immutable struct defined in a way that FlatSharp can use.
[FlatBufferTable]
public class Person
{
protected Person(FlatBufferDeserializationContext context) { }
public Person(string name, Location location)
{
this.Name = name;
this.Location = location;
}
[FlatBufferItem(0)] public virtual string? Name { get; }
[FlatBufferItem(1)] public virtual Location? Location { get; }
}
[FlatBufferStruct]
public class Location
{
protected Location(FlatBufferDeserializationContext context) { }
public Location(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
// Non-Virtual properties must specify a setter
[FlatBufferItem(0)] public float X { get; protected init; }
[FlatBufferItem(1)] public float Y { get; protected init; }
[FlatBufferItem(2)] public float Z { get; protected init; }
}
An FBS file for the example above looks like:
table Person (fs_defaultCtor:"None")
{
Name:string (fs_setter:"None");
Location:Location (fs_setter:"None");
}
// Structs in FBS files require a default constructor, but we declare it to be obsolete in this case
// to trigger a compiler warning if it is used.
struct Location (fs_defaultCtor:"PublicObsolete", fs_nonVirtual)
{
X:float (fs_setter:"Protected");
Y:float (fs_setter:"Protected");
Z:float (fs_setter:"Protected");
}
To get the same constructors from the C# example above, we need to define them in partial classes:
public partial class Person
{
public Person(string name, Location location)
{
this.Name = name;
this.Location = location;
}
}
public partial class Location
{
public Location(float x, float y, float z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
}