Skip to content

Defining Immutable Objects

James Courtney edited this page Mar 4, 2021 · 12 revisions

NOTE: THIS DOCUMENT APPLIES ONLY TO FLATSHARP 5.0 AND ABOVE. FLATSHARP 5 IS NOT YET RELEASED.

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.

Deserialization Modes

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.

Defining Immutable Tables and Structs

FlatSharp also supports defining immutable tables and structs, using a few different patterns:

Init-Only Properties (C# 9)

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; }

Non-Public Setters

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; }

Constructor Options

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() { }
}

Full Example (C#)

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; }
}

Full Example (FBS)

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;
   }
}
Clone this wiki locally