IReadOnly
IReadOnly is an interface for classes that can be changed from initial mutable state into immutable.
/// <summary>Interface for objects whose state can be locked into read-only state.</summary>
public interface IReadOnly
{
/// <summary>Is class immutable.</summary>
/// <exception cref="InvalidOperationException">If writability state change is not allowed.</exception>
[IgnoreDataMember]
bool ReadOnly { get; set; }
}
Extension method .SetReadOnly() (namespace Avalanche.Utilities, Avalanche.Utilities.Abstractions.dll) sets the class into read-only immutable state.
MyClass myClass = new MyClass { Id = 5, Label = "ABC" }.SetReadOnly();
.SetElementsReadOnly() assigns elements into read-only state.
// Create records
MyRecord[] records =
new MyRecord[]
{
new MyRecord { Id = 1, Label = "A" },
new MyRecord { Id = 2, Label = "B" },
new MyRecord { Id = 3, Label = "C" }
}.SetElementsReadOnly();
Class Implementation
The read-only property in the implementation should use [IgnoreDataMember] attribute to indicate that it is not part of the data for serialization.
public class MyClass : IReadOnly
{
/// <summary>Is read-only state</summary>
[IgnoreDataMember] protected bool @readonly;
/// <summary>Is read-only state</summary>
[IgnoreDataMember] bool IReadOnly.ReadOnly { get => @readonly; set { if (@readonly == value) return; if (!value) CoreMessages.Instance.BadReadOnly.Throw(); @readonly = true; } }
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
}
Implementation can use extension method AssertWritable<T>() (namespace Avalanche.Utilities, Avalanche.Core.dll) to assert the class is in immutable state.
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
Class can also inherit the ReadOnlyAssignableClass base class.
public class MyClass2 : ReadOnlyAssignableClass
{
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
}
Record Implementation
Records can inherit ReadOnlyAssignableRecord which excludes 'readonly' field when cloning with 'with' operator.
public record MyRecord : ReadOnlyAssignableRecord
{
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
}
This allows to mutate record using the 'with' clone.
// Create record
MyRecord myRecord = new MyRecord { Id = 5, Label = "ABC" }.SetReadOnly();
// Clone using 'with' operator
MyRecord myRecord2 = (myRecord with { Label = "XYZ " }).SetReadOnly();
Service
Service handler class Avalanche.Service.LockerHandler<T> modifies result of T into read-only state. This enables other handlers to participate in the result while in a mutable state and, at end of query, result is locked into immutable state.
/// <summary>List of accessor handlers</summary>
public record MyHandlerTable : HandlersTable
{
/// <summary>MyClass handler</summary>
public MyClassHandler MyClassHandler { get; init; } = new();
/// <summary>Locks <see cref="MyClass"/> to read-only state at end of query.</summary>
public LockerHandler<MyClass> MyClassLocker { get; init; } = new();
}
Full Example
Full example
using System;
using System.Runtime.Serialization;
using Avalanche.Service;
using Avalanche.Utilities;
using Avalanche.Message;
class @readonly
{
public static void Run()
{
{
// <01>
MyClass myClass = new MyClass { Id = 5, Label = "ABC" }.SetReadOnly();
// </01>
}
{
// <02>
// Create record
MyRecord myRecord = new MyRecord { Id = 5, Label = "ABC" }.SetReadOnly();
// Clone using 'with' operator
MyRecord myRecord2 = (myRecord with { Label = "XYZ " }).SetReadOnly();
// </02>
}
{
// <03>
// Create records
MyRecord[] records =
new MyRecord[]
{
new MyRecord { Id = 1, Label = "A" },
new MyRecord { Id = 2, Label = "B" },
new MyRecord { Id = 3, Label = "C" }
}.SetElementsReadOnly();
// </03>
}
}
// <90>
public class MyClass : IReadOnly
{
/// <summary>Is read-only state</summary>
[IgnoreDataMember] protected bool @readonly;
/// <summary>Is read-only state</summary>
[IgnoreDataMember] bool IReadOnly.ReadOnly { get => @readonly; set { if (@readonly == value) return; if (!value) CoreMessages.Instance.BadReadOnly.Throw(); @readonly = true; } }
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
// <91>
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
// </91>
}
// </90>
// <92>
public record MyRecord : ReadOnlyAssignableRecord
{
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
}
// </92>
// <93>
public class MyClass2 : ReadOnlyAssignableClass
{
/// <summary>Id</summary>
protected int id;
/// <summary>Label</summary>
protected string label = null!;
/// <summary>Id</summary>
public int Id { get => id; set => this.AssertWritable().id = value; }
/// <summary>Label</summary>
public string Label { get => label; set => this.AssertWritable().label = value; }
}
// </93>
// <99>
/// <summary>List of accessor handlers</summary>
public record MyHandlerTable : HandlersTable
{
/// <summary>MyClass handler</summary>
public MyClassHandler MyClassHandler { get; init; } = new();
/// <summary>Locks <see cref="MyClass"/> to read-only state at end of query.</summary>
public LockerHandler<MyClass> MyClassLocker { get; init; } = new();
}
// </99>
internal class MyClassHandler : IHandler<object, MyClass>
{
public void Handle(IQuery<object, MyClass> query) { }
}
}