Update the immutibility names. Add net.10.0

This commit is contained in:
Marc Hernandez 2026-01-31 15:18:33 -08:00
parent c25b9b3b12
commit 65e7ed1b24
16 changed files with 448 additions and 451 deletions

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net9</TargetFrameworks> <TargetFrameworks>net9.0;net10.0</TargetFrameworks>
<RootNamespace>lib</RootNamespace> <RootNamespace>lib</RootNamespace>
<AssemblyVersion>0.0.1.0</AssemblyVersion> <AssemblyVersion>0.0.1.0</AssemblyVersion>
<FileVersion>0.0.1.0</FileVersion> <FileVersion>0.0.1.0</FileVersion>

View File

@ -1,11 +1,10 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using imm;
namespace exp; namespace exp;
abstract public record class Exp<T> : imm.Versioned<Exp<T>> abstract public record class Exp<T> : io.Versioned<Exp<T>>
{ {
protected Exp() protected Exp()
: :

View File

@ -5,24 +5,24 @@ using System.Runtime.CompilerServices;
/// <summary> /// <summary>
/// Base context for an FSM. /// Base context for an FSM.
/// MUST inherit from imm.Recorded<TSelf> or Timed<TSelf> in your concrete class. /// MUST inherit from io.Recorded<TSelf> or Timed<TSelf> in your concrete class.
/// </summary> /// </summary>
/// <typeparam name="TSelf">The concrete Context type.</typeparam> /// <typeparam name="TSelf">The concrete Context type.</typeparam>
public abstract record class FsmContextBase<TSelf> : imm.Recorded<TSelf> public abstract record class FsmContextBase<TSelf> : io.Recorded<TSelf>
where TSelf : FsmContextBase<TSelf> where TSelf : FsmContextBase<TSelf>
{ {
// Required for 'with' expressions. // Required for 'with' expressions.
protected FsmContextBase(imm.Recorded<TSelf> original) : base(original) { } protected FsmContextBase(io.Recorded<TSelf> original) : base(original) { }
protected FsmContextBase() { } protected FsmContextBase() { }
} }
/// <summary> /// <summary>
/// Base state for an FSM. /// Base state for an FSM.
/// MUST inherit from imm.Recorded<TSelf> or Timed<TSelf> in your concrete class. /// MUST inherit from io.Recorded<TSelf> or Timed<TSelf> in your concrete class.
/// </summary> /// </summary>
/// <typeparam name="TSelf">The concrete State type.</typeparam> /// <typeparam name="TSelf">The concrete State type.</typeparam>
/// <typeparam name="TCtx">The concrete Context type (must be based on FsmContextBase).</typeparam> /// <typeparam name="TCtx">The concrete Context type (must be based on FsmContextBase).</typeparam>
public abstract record class FsmStateBase<TSelf, TCtx> : imm.Recorded<TSelf> public abstract record class FsmStateBase<TSelf, TCtx> : io.Recorded<TSelf>
where TSelf : FsmStateBase<TSelf, TCtx> where TSelf : FsmStateBase<TSelf, TCtx>
where TCtx : FsmContextBase<TCtx> where TCtx : FsmContextBase<TCtx>
{ {
@ -43,18 +43,18 @@ public abstract record class FsmStateBase<TSelf, TCtx> : imm.Recorded<TSelf>
} }
// Required for 'with' expressions. // Required for 'with' expressions.
protected FsmStateBase(imm.Recorded<TSelf> original) : base(original) { } protected FsmStateBase(io.Recorded<TSelf> original) : base(original) { }
protected FsmStateBase() { } protected FsmStateBase() { }
} }
/// <summary> /// <summary>
/// An immutable FSM base class. /// An immutable FSM base class.
/// MUST inherit from imm.Recorded<TSelf> or Timed<TSelf> in your concrete class. /// MUST inherit from io.Recorded<TSelf> or Timed<TSelf> in your concrete class.
/// </summary> /// </summary>
/// <typeparam name="TSelf">The concrete FSM type.</typeparam> /// <typeparam name="TSelf">The concrete FSM type.</typeparam>
/// <typeparam name="TState">The concrete State type.</typeparam> /// <typeparam name="TState">The concrete State type.</typeparam>
/// <typeparam name="TCtx">The concrete Context type.</typeparam> /// <typeparam name="TCtx">The concrete Context type.</typeparam>
public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf> public abstract record class FsmBase<TSelf, TState, TCtx> : io.Recorded<TSelf>
where TSelf : FsmBase<TSelf, TState, TCtx> where TSelf : FsmBase<TSelf, TState, TCtx>
where TState : FsmStateBase<TState, TCtx> where TState : FsmStateBase<TState, TCtx>
where TCtx : FsmContextBase<TCtx> where TCtx : FsmContextBase<TCtx>
@ -69,7 +69,7 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
} }
// Required for 'with' expressions. // Required for 'with' expressions.
protected FsmBase(imm.Recorded<TSelf> original) : base(original) protected FsmBase(io.Recorded<TSelf> original) : base(original)
{ {
var o = original as FsmBase<TSelf, TState, TCtx>; var o = original as FsmBase<TSelf, TState, TCtx>;
Context = o!.Context; Context = o!.Context;
@ -78,7 +78,7 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
/// <summary> /// <summary>
/// Transitions the FSM. It automatically uses the 'Process' /// Transitions the FSM. It automatically uses the 'Process'
/// method appropriate for imm.Recorded or Timed, thanks to virtual overrides. /// method appropriate for io.Recorded or Timed, thanks to virtual overrides.
/// </summary> /// </summary>
public TSelf Transition( public TSelf Transition(
TState newState, TState newState,
@ -93,7 +93,7 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
var (ctxAfterExit, stateAfterExit) = State.OnExit(Context, newState); var (ctxAfterExit, stateAfterExit) = State.OnExit(Context, newState);
var (ctxAfterEnter, stateAfterEnter) = newState.OnEnter(ctxAfterExit, stateAfterExit); var (ctxAfterEnter, stateAfterEnter) = newState.OnEnter(ctxAfterExit, stateAfterExit);
// Since 'this' is at least 'imm.Recorded<TSelf>', we can call the // Since 'this' is at least 'io.Recorded<TSelf>', we can call the
// detailed 'Process'. If 'this' is actually 'Timed<TSelf>', C#'s // detailed 'Process'. If 'this' is actually 'Timed<TSelf>', C#'s
// virtual dispatch will call the 'Timed' override automatically. // virtual dispatch will call the 'Timed' override automatically.
return Process( return Process(
@ -106,4 +106,4 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
memberName, filePath, lineNumber, expression memberName, filePath, lineNumber, expression
); );
} }
} }

View File

@ -1,366 +1,30 @@
#nullable enable //#nullable enable
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace imm;
/// <summary> /// <summary>
/// Represents the base interface for versioned, immutable objects. /// Helper static class for processing immutable objects using a 'ref' pattern.
/// Provides access to metadata and potentially the previous version. /// Provides different levels of processing based on the type.
/// </summary> /// </summary>
public interface Obj public static class imm
{ {
/// <summary> /// <summary>
/// Gets the base metadata associated with this version. /// Processes a 'Versioned' object (Level 1).
/// </summary> /// </summary>
Metadata_Versioned Meta { get; } public static T LightProcess<T>(
ref T obj,
/// <summary>
/// Gets the previous version as a base object, if available.
/// Returns null if this is the first version or if history is not tracked.
/// </summary>
Obj? Old { get; }
/// <summary>
/// Creates a new version without functional change.
/// Returns the new version as an Obj.
/// </summary>
Obj Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 );
}
/// <summary>
/// Obj delegate for change notifications.
/// </summary>
public delegate void ChangeDelegate<T>( T? oldVersion, T newVersion );
/// <summary>
/// Represents a generic interface for immutable objects,
/// providing access to basic processing functions and change notifications.
/// </summary>
public interface Obj<T> : Obj where T : Obj<T>
{
/// <summary>
/// Gets the change delegate associated with this object.
/// </summary>
[JsonIgnore]
ChangeDelegate<T> OnChange { get; set; }
/// <summary>
/// Applies a transformation and creates a new version using basic processing.
/// </summary>
T Process(
Func<T, T> fn, Func<T, T> fn,
string reason = "Processed", string reason = "Processed" )
[CallerMemberName] string dbgName = "", where T : io.Versioned<T>
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" );
/// <summary>
/// Creates a new version without a functional change using basic processing.
/// Uses 'new' to provide a type-safe return.
/// </summary>
new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 );
}
/*
static public class ObjExtensions
{
/// <summary>
/// Creates a new version of the object with the specified reason.
/// </summary>
public static T Record<T>( this T obj, string reason = "Recorded" ) where T : Obj
{ {
if( obj is Recorded<T> recorded ) obj = obj.Process( fn, reason );
{ return obj;
return recorded.Record( reason );
}
else if( obj is Versioned<T> versioned )
{
return versioned.Record( reason );
}
else
{
// Dont care
return obj;
}
}
}
*/
// --- Metadata Hierarchy ---
public interface VersionedMeta
{
public uint Version { get; }
public string Reason { get; }
}
/// <summary>
/// Obj metadata for version tracking.
/// </summary>
public record Metadata_Versioned
{
public uint Version { get; init; } = 1;
public string Reason { get; init; } = "Created";
}
public interface RecordedMeta : VersionedMeta
{
public string MemberName { get; }
public string FilePath { get; }
public int LineNumber { get; }
public string Expression { get; }
}
/// <summary>
/// Metadata for version and recording (debug/caller info, history).
/// </summary>
public record Metadata_Recorded : Metadata_Versioned, RecordedMeta
{
internal object? OldObject { get; init; } = null;
public string MemberName { get; init; } = "";
public string FilePath { get; init; } = "";
public int LineNumber { get; init; } = 0;
public string Expression { get; init; } = "";
}
public interface TimedMeta : RecordedMeta
{
public DateTime CreatedAt { get; }
public DateTime TouchedAt { get; }
}
/// <summary>
/// Metadata for version, recording, and timing.
/// </summary>
public record Metadata_Timed : Metadata_Recorded, TimedMeta
{
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public DateTime TouchedAt { get; init; } = DateTime.UtcNow;
}
// --- Record Hierarchy ---
/// <summary>
/// Level 1: Basic versioning. Implements Obj<T>.
/// </summary>
public record class Versioned<T> : Obj<T> where T : Versioned<T>
{
public Metadata_Versioned Meta { get; init; } = new();
[DebuggerBrowsable( DebuggerBrowsableState.Never )]
[JsonIgnore]
public ChangeDelegate<T> OnChange { get; set; } = ( o, n ) => { };
public virtual Obj? Old => null;
Metadata_Versioned Obj.Meta => this.Meta;
[JsonIgnore]
Obj? Obj.Old => this.Old;
public Versioned() { }
protected Versioned( Versioned<T> original )
{
OnChange = original.OnChange;
Meta = original.Meta;
}
public virtual T Process(
Func<T, T> fn,
string reason = "Processed",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
var newVersion = next with
{
Meta = new Metadata_Versioned { /*...*/ },
OnChange = current.OnChange
};
newVersion.OnChange( current, newVersion );
return newVersion;
} }
/// <summary> /// <summary>
/// Basic Record. Made virtual. Implements Obj<T>.Record. /// Processes a 'Recorded' object (Level 2), capturing caller info.
/// </summary> /// </summary>
public virtual T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgName, dbgPath, dbgLine );
/// <summary>
/// Implements Obj.Record by calling the virtual T Record.
/// </summary>
Obj Obj.Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgName, dbgPath, dbgLine );
}
/// <summary>
/// Level 2: Adds history and caller info.
/// </summary>
public record class Recorded<T> : Versioned<T> where T : Recorded<T>
{
new public Metadata_Recorded Meta { get; init; } = new();
[JsonIgnore]
new public T? Old => Meta.OldObject as T;
//public override Obj? Old => this.Old;
//Metadata_Versioned Obj.Meta => this.Meta;
public Recorded() { }
protected Recorded( Recorded<T> original ) : base( original ) { Meta = original.Meta; }
public override T Process(
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
var newMeta = current.Meta with {
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgName,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = expStr,
OldObject = current,
};
var newVersion = next with { Meta = newMeta, OnChange = current.OnChange };
newVersion.OnChange( current, newVersion );
return newVersion;
}
public new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 )
{
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
}
}
/// <summary>
/// Level 3: Adds timestamps.
/// </summary>
public record class Timed<T> : Recorded<T> where T : Timed<T>
{
new public Metadata_Timed Meta { get; init; } = new();
//Metadata_Versioned Obj.Meta => this.Meta;
public TimeSpan SinceLastTouch => Meta.TouchedAt - ( Old?.Meta as Metadata_Timed )?.TouchedAt ?? TimeSpan.Zero;
public TimeSpan TotalAge => Meta.TouchedAt - Meta.CreatedAt;
public Timed() { }
protected Timed( Timed<T> original ) : base( original ) { Meta = original.Meta; }
public override T Process(
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
/*
var newMeta = new Metadata_Timed
{
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgMethod,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = dbgExpression,
OldObject = current,
CreatedAt = current.Meta.CreatedAt,
TouchedAt = DateTime.UtcNow
};
*/
var newMeta = current.Meta with
{
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgMethod,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = dbgExpStr,
OldObject = current,
TouchedAt = DateTime.UtcNow
};
// Testing: Shouldnt need the OnChange
var newVersion = next with { Meta = newMeta };
newVersion.OnChange( current, newVersion );
return newVersion;
}
public new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 )
{
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
}
}
/*
public static class TimedExt
{
public static T Process<T>( public static T Process<T>(
ref T obj, ref T obj,
Func<T, T> fn, Func<T, T> fn,
@ -368,11 +32,20 @@ public static class TimedExt
[CallerMemberName] string dbgName = "", [CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "", [CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0, [CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpression = "" [CallerArgumentExpression( "fn" )] string dbgExpression = "" )
) where T : imm.Timed<T> where T : io.Recorded<T> // Catches Recorded and Timed
{ {
// This will call the 'Timed' override if T is Timed,
// or the 'Recorded' override if T is Recorded.
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression ); obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
return obj; return obj;
} }
public static string LogDiff<T>( T cur, T old, [CallerArgumentExpression( "cur" )] string dbgExpCur = "", [CallerArgumentExpression( "old" )] string dbgExpOld = "" )
{
return $"{dbgExpCur} changed from {old} to {cur}";
}
// No specific Process needed for Timed, as it's caught by Recorded<T>
// and its Process override handles the timing.
} }
*/

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace imm; namespace io;
/// <summary> /// <summary>
/// An immutable list implementation that tracks history, metadata, and time. /// An immutable list implementation that tracks history, metadata, and time.
@ -79,4 +79,4 @@ public record class List<T> : Timed<List<T>>, IImmutableList<T>
// --- Standard Interfaces --- // --- Standard Interfaces ---
public IEnumerator<T> GetEnumerator() => Values.GetEnumerator(); public IEnumerator<T> GetEnumerator() => Values.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Values).GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)Values).GetEnumerator();
} }

View File

@ -1,4 +1,2 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace imm;

378
imm/io.cs Normal file
View File

@ -0,0 +1,378 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace io;
/// <summary>
/// Represents the base interface for versioned, immutable objects.
/// Provides access to metadata and potentially the previous version.
/// </summary>
public interface Obj
{
/// <summary>
/// Gets the base metadata associated with this version.
/// </summary>
Metadata_Versioned Meta { get; }
/// <summary>
/// Gets the previous version as a base object, if available.
/// Returns null if this is the first version or if history is not tracked.
/// </summary>
Obj? Old { get; }
/// <summary>
/// Creates a new version without functional change.
/// Returns the new version as an Obj.
/// </summary>
Obj Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 );
}
/// <summary>
/// Obj delegate for change notifications.
/// </summary>
public delegate void ChangeDelegate<T>( T? oldVersion, T newVersion );
/// <summary>
/// Represents a generic interface for immutable objects,
/// providing access to basic processing functions and change notifications.
/// </summary>
public interface Obj<T> : Obj where T : Obj<T>
{
/// <summary>
/// Gets the change delegate associated with this object.
/// </summary>
[JsonIgnore]
ChangeDelegate<T> OnChange { get; set; }
/// <summary>
/// Applies a transformation and creates a new version using basic processing.
/// </summary>
T Process(
Func<T, T> fn,
string reason = "Processed",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" );
/// <summary>
/// Creates a new version without a functional change using basic processing.
/// Uses 'new' to provide a type-safe return.
/// </summary>
new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 );
}
/*
static public class ObjExtensions
{
/// <summary>
/// Creates a new version of the object with the specified reason.
/// </summary>
public static T Record<T>( this T obj, string reason = "Recorded" ) where T : Obj
{
if( obj is Recorded<T> recorded )
{
return recorded.Record( reason );
}
else if( obj is Versioned<T> versioned )
{
return versioned.Record( reason );
}
else
{
// Dont care
return obj;
}
}
}
*/
// --- Metadata Hierarchy ---
public interface VersionedMeta
{
public uint Version { get; }
public string Reason { get; }
}
/// <summary>
/// Obj metadata for version tracking.
/// </summary>
public record Metadata_Versioned
{
public uint Version { get; init; } = 1;
public string Reason { get; init; } = "Created";
}
public interface RecordedMeta : VersionedMeta
{
public string MemberName { get; }
public string FilePath { get; }
public int LineNumber { get; }
public string Expression { get; }
}
/// <summary>
/// Metadata for version and recording (debug/caller info, history).
/// </summary>
public record Metadata_Recorded : Metadata_Versioned, RecordedMeta
{
internal object? OldObject { get; init; } = null;
public string MemberName { get; init; } = "";
public string FilePath { get; init; } = "";
public int LineNumber { get; init; } = 0;
public string Expression { get; init; } = "";
}
public interface TimedMeta : RecordedMeta
{
public DateTime CreatedAt { get; }
public DateTime TouchedAt { get; }
}
/// <summary>
/// Metadata for version, recording, and timing.
/// </summary>
public record Metadata_Timed : Metadata_Recorded, TimedMeta
{
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public DateTime TouchedAt { get; init; } = DateTime.UtcNow;
}
// --- Record Hierarchy ---
/// <summary>
/// Level 1: Basic versioning. Implements Obj<T>.
/// </summary>
public record class Versioned<T> : Obj<T> where T : Versioned<T>
{
public Metadata_Versioned Meta { get; init; } = new();
[DebuggerBrowsable( DebuggerBrowsableState.Never )]
[JsonIgnore]
public ChangeDelegate<T> OnChange { get; set; } = ( o, n ) => { };
public virtual Obj? Old => null;
Metadata_Versioned Obj.Meta => this.Meta;
[JsonIgnore]
Obj? Obj.Old => this.Old;
public Versioned() { }
protected Versioned( Versioned<T> original )
{
OnChange = original.OnChange;
Meta = original.Meta;
}
public virtual T Process(
Func<T, T> fn,
string reason = "Processed",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
var newVersion = next with
{
Meta = new Metadata_Versioned { /*...*/ },
OnChange = current.OnChange
};
newVersion.OnChange( current, newVersion );
return newVersion;
}
/// <summary>
/// Basic Record. Made virtual. Implements Obj<T>.Record.
/// </summary>
public virtual T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) => Process( t => t, reason, dbgName, dbgPath, dbgLine );
/// <summary>
/// Implements Obj.Record by calling the virtual T Record.
/// </summary>
Obj Obj.Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 ) => this.Record( reason, dbgName, dbgPath, dbgLine );
}
/// <summary>
/// Level 2: Adds history and caller info.
/// </summary>
public record class Recorded<T> : Versioned<T> where T : Recorded<T>
{
new public Metadata_Recorded Meta { get; init; } = new();
[JsonIgnore]
new public T? Old => Meta.OldObject as T;
//public override Obj? Old => this.Old;
//Metadata_Versioned Obj.Meta => this.Meta;
public Recorded() { }
protected Recorded( Recorded<T> original ) : base( original ) { Meta = original.Meta; }
public override T Process(
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string expStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
var newMeta = current.Meta with {
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgName,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = expStr,
OldObject = current,
};
var newVersion = next with { Meta = newMeta, OnChange = current.OnChange };
newVersion.OnChange( current, newVersion );
return newVersion;
}
public new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 )
{
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
}
}
/// <summary>
/// Level 3: Adds timestamps.
/// </summary>
public record class Timed<T> : Recorded<T> where T : Timed<T>
{
new public Metadata_Timed Meta { get; init; } = new();
//Metadata_Versioned Obj.Meta => this.Meta;
public TimeSpan SinceLastTouch => Meta.TouchedAt - ( Old?.Meta as Metadata_Timed )?.TouchedAt ?? TimeSpan.Zero;
public TimeSpan TotalAge => Meta.TouchedAt - Meta.CreatedAt;
public Timed() { }
protected Timed( Timed<T> original ) : base( original ) { Meta = original.Meta; }
public override T Process(
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgMethod = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpStr = "" )
{
var current = (T)this;
var next = fn( current );
if( ReferenceEquals( current, next ) )
return current;
/*
var newMeta = new Metadata_Timed
{
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgMethod,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = dbgExpression,
OldObject = current,
CreatedAt = current.Meta.CreatedAt,
TouchedAt = DateTime.UtcNow
};
*/
var newMeta = current.Meta with
{
Version = current.Meta.Version + 1,
Reason = reason,
MemberName = dbgMethod,
FilePath = dbgPath,
LineNumber = dbgLine,
Expression = dbgExpStr,
OldObject = current,
TouchedAt = DateTime.UtcNow
};
// Testing: Shouldnt need the OnChange
var newVersion = next with { Meta = newMeta };
newVersion.OnChange( current, newVersion );
return newVersion;
}
public new T Record(
string reason = "Recorded",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0 )
{
return Process( t => t, reason, dbgName, dbgPath, dbgLine );
}
}
/*
public static class TimedExt
{
public static T Process<T>(
ref T obj,
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpression = ""
) where T : io.Timed<T>
{
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
return obj;
}
}
*/

View File

@ -1,52 +0,0 @@
//#nullable enable
using System;
using System.Runtime.CompilerServices;
using imm;
/// <summary>
/// Helper static class for processing immutable objects using a 'ref' pattern.
/// Provides different levels of processing based on the type.
/// </summary>
public static class iu
{
/// <summary>
/// Processes a 'Versioned' object (Level 1).
/// </summary>
public static T LightProcess<T>(
ref T obj,
Func<T, T> fn,
string reason = "Processed" )
where T : Versioned<T>
{
obj = obj.Process( fn, reason );
return obj;
}
/// <summary>
/// Processes a 'Recorded' object (Level 2), capturing caller info.
/// </summary>
public static T Process<T>(
ref T obj,
Func<T, T> fn,
string reason = "",
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression( "fn" )] string dbgExpression = "" )
where T : Recorded<T> // Catches Recorded and Timed
{
// This will call the 'Timed' override if T is Timed,
// or the 'Recorded' override if T is Recorded.
obj = obj.Process( fn, reason, dbgName, dbgPath, dbgLine, dbgExpression );
return obj;
}
public static string LogDiff<T>( T cur, T old, [CallerArgumentExpression( "cur" )] string dbgExpCur = "", [CallerArgumentExpression( "old" )] string dbgExpOld = "" )
{
return $"{dbgExpCur} changed from {old} to {cur}";
}
// No specific Process needed for Timed, as it's caught by Recorded<T>
// and its Process override handles the timing.
}

View File

@ -17,7 +17,7 @@ using System.Collections.Immutable;
namespace ser; namespace ser;
public record CodeGenConfig : imm.Recorded<CodeGenConfig> public record CodeGenConfig : io.Recorded<CodeGenConfig>
{ {
// Whitelists (if needed, otherwise rely on attributes/defaults) // Whitelists (if needed, otherwise rely on attributes/defaults)
public ImmutableDictionary<string, ImmutableList<string>> WLProps { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty; public ImmutableDictionary<string, ImmutableList<string>> WLProps { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;

View File

@ -490,7 +490,7 @@ namespace lib
{ {
if( _cfg.VerboseLogging ) log.info( $"" ); if( _cfg.VerboseLogging ) log.info( $"" );
var isImm = typeof(imm.Obj).IsAssignableFrom( narrowType ); var isImm = typeof(io.Obj).IsAssignableFrom( narrowType );
XmlNodeList allChildren = elem.ChildNodes; XmlNodeList allChildren = elem.ChildNodes;
@ -648,8 +648,8 @@ namespace lib
} }
else else
{ {
var imm = obj as imm.Obj; var immObj = obj as io.Obj;
var newObj = imm.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" ); var newObj = immObj.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" );
return newObj; return newObj;
} }
@ -1290,7 +1290,7 @@ namespace lib
HashSet<string> whitelistFields, whitelistProps; HashSet<string> whitelistFields, whitelistProps;
GetFilters( _cfg.TypesDefault, mi, narrowType, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps ); GetFilters( _cfg.TypesDefault, mi, narrowType, out filterFields, out filterProps, out doImpls, out doFields, out doProps, out whitelistFields, out whitelistProps );
var isImm = typeof(imm.Obj).IsAssignableFrom( narrowType ); var isImm = typeof(io.Obj).IsAssignableFrom( narrowType );
if( doFields || doImpls ) if( doFields || doImpls )
{ {

View File

@ -32,6 +32,7 @@ N O T  D O I N G :
*/ */
#region Helpers
public record struct Value<T>( T _val, string _exp = "" ) public record struct Value<T>( T _val, string _exp = "" )
{ {
public static T Default = default!; public static T Default = default!;
@ -87,13 +88,10 @@ public struct SourceLoc
} }
#endregion // Helpers
static public class log static public class log
{ {
//static
#region CLR Logging #region CLR Logging
@ -155,14 +153,6 @@ static public class log
#endregion // CLR Logging #endregion // CLR Logging
static public Value<T> Value<T>( T val,
[CallerArgumentExpression("val")]
string dbgExp = ""
)
{
return new( val, dbgExp );
}
[Flags] [Flags]
public enum LogType public enum LogType
{ {
@ -184,13 +174,15 @@ static public class log
{ {
None = 0, None = 0,
File = 1 << 0, File = 1 << 0,
Console = 1 << 1, Console = 1 << 1,
All = File | Console, All = File | Console,
} }
#region LogEvent
public struct LogEvent public struct LogEvent
{ {
public DateTime Time; public DateTime Time;
@ -264,7 +256,7 @@ static public class log
ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, ( k, v ) => ep ); ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, ( k, v ) => ep );
} }
#endregion // LogEvent
static public void shutdown() static public void shutdown()
{ {
@ -1020,4 +1012,13 @@ static public class log
private static ArrayList s_delegates = new ArrayList(); private static ArrayList s_delegates = new ArrayList();
} static public Value<T> Value<T>( T val,
[CallerArgumentExpression("val")]
string dbgExp = ""
)
{
return new( val, dbgExp );
}
} // end static class log

View File

@ -182,7 +182,7 @@ public class Ref<T> : Ref where T : class, new()
// Let's assume you'll add saving logic here. // Let's assume you'll add saving logic here.
// Mgr.Save(value, path); // Example: Needs implementation // Mgr.Save(value, path); // Example: Needs implementation
var immMeta = (value as imm.Obj)?.Meta; var immMeta = (value as io.Obj)?.Meta;
@ -365,7 +365,7 @@ public static class Mgr
{ {
return cachedValue; return cachedValue;
} }
// 2. Get a lock specific to this filename and lock it. // 2. Get a lock specific to this filename and lock it.
var fileLock = s_loadingLocks.GetOrAdd( filename, _ => new object() ); var fileLock = s_loadingLocks.GetOrAdd( filename, _ => new object() );
lock( fileLock ) lock( fileLock )
@ -433,10 +433,10 @@ public static class Mgr
var loadedObject = loaderHolder.Load( filename, reason, dbgName, dbgPath, dbgLine ); var loadedObject = loaderHolder.Load( filename, reason, dbgName, dbgPath, dbgLine );
if( loadedObject is T value ) if( loadedObject is T value )
{ {
var meta = (value as imm.Obj)?.Meta; var meta = (value as io.Obj)?.Meta;
// If it's an immutable object, record its loading. // If it's an immutable object, record its loading.
if( value is imm.Obj imm ) if( value is io.Obj imm )
{ {
return (T)imm.Record( $"Loading bcs {reason}", dbgName, dbgPath, dbgLine ); return (T)imm.Record( $"Loading bcs {reason}", dbgName, dbgPath, dbgLine );
} }

View File

@ -90,7 +90,7 @@ public enum BackingFieldNaming { Short, Regular }
public enum POD { Attributes, Elements } public enum POD { Attributes, Elements }
public record struct TypeProxy( Func<object, string> fnSer, Func<string, string, object> fnDes ); public record struct TypeProxy( Func<object, string> fnSer, Func<string, string, object> fnDes );
public record XmlCfg : imm.Recorded<XmlCfg> public record XmlCfg : io.Recorded<XmlCfg>
{ {
public bool Verbose { get; init; } = false; public bool Verbose { get; init; } = false;
public Datastructure Structure { get; init; } = Datastructure.Tree; public Datastructure Structure { get; init; } = Datastructure.Tree;
@ -254,7 +254,7 @@ public class TypeMetaCache
GetFilters( _cfg.TypesDefault, type, out doImpls, out doFields, out doProps ); GetFilters( _cfg.TypesDefault, type, out doImpls, out doFields, out doProps );
var isImm = typeof( imm.Obj ).IsAssignableFrom( type ); var isImm = typeof( io.Obj ).IsAssignableFrom( type );
var typesAtt = type.GetCustomAttribute<ser.Ser>( true ); var typesAtt = type.GetCustomAttribute<ser.Ser>( true );
var serTypes = typesAtt?.Types ?? ser.Types.None; var serTypes = typesAtt?.Types ?? ser.Types.None;

View File

@ -179,7 +179,7 @@ public partial class ObjectHandler : ITypeHandler
// 3. Post-processing // 3. Post-processing
if( obj is ser.I_Serialize iSer ) if( obj is ser.I_Serialize iSer )
obj = iSer.OnDeserialize( null ); obj = iSer.OnDeserialize( null );
if( ti.IsImm && obj is imm.Obj immObj ) if( ti.IsImm && obj is io.Obj immObj )
return immObj.Record( $"From XML {elem.Name}" ); return immObj.Record( $"From XML {elem.Name}" );
return obj; return obj;

View File

@ -9,7 +9,7 @@ using System.Text;
namespace ser; namespace ser;
public record class SimpleImmutable( string Name, int Age ) : imm.Timed<SimpleImmutable>; public record class SimpleImmutable( string Name, int Age ) : io.Timed<SimpleImmutable>;
static public class Test static public class Test
{ {

View File

@ -10,7 +10,7 @@ using System.IO;
namespace test; namespace test;
public record class SimpleImmutable( string Name, int Age ) : imm.Timed<SimpleImmutable>; public record class SimpleImmutable( string Name, int Age ) : io.Timed<SimpleImmutable>;
static public class XmlFormatter2 static public class XmlFormatter2
{ {