Update the immutibility names. Add net.10.0
This commit is contained in:
parent
c25b9b3b12
commit
65e7ed1b24
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9</TargetFrameworks>
|
||||
<TargetFrameworks>net9.0;net10.0</TargetFrameworks>
|
||||
<RootNamespace>lib</RootNamespace>
|
||||
<AssemblyVersion>0.0.1.0</AssemblyVersion>
|
||||
<FileVersion>0.0.1.0</FileVersion>
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
using imm;
|
||||
|
||||
namespace exp;
|
||||
|
||||
|
||||
abstract public record class Exp<T> : imm.Versioned<Exp<T>>
|
||||
abstract public record class Exp<T> : io.Versioned<Exp<T>>
|
||||
{
|
||||
protected Exp()
|
||||
:
|
||||
|
||||
22
imm/FSM.cs
22
imm/FSM.cs
@ -5,24 +5,24 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <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>
|
||||
{
|
||||
// Required for 'with' expressions.
|
||||
protected FsmContextBase(imm.Recorded<TSelf> original) : base(original) { }
|
||||
protected FsmContextBase(io.Recorded<TSelf> original) : base(original) { }
|
||||
protected FsmContextBase() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <typeparam name="TSelf">The concrete State type.</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 TCtx : FsmContextBase<TCtx>
|
||||
{
|
||||
@ -43,18 +43,18 @@ public abstract record class FsmStateBase<TSelf, TCtx> : imm.Recorded<TSelf>
|
||||
}
|
||||
|
||||
// Required for 'with' expressions.
|
||||
protected FsmStateBase(imm.Recorded<TSelf> original) : base(original) { }
|
||||
protected FsmStateBase(io.Recorded<TSelf> original) : base(original) { }
|
||||
protected FsmStateBase() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <typeparam name="TSelf">The concrete FSM type.</typeparam>
|
||||
/// <typeparam name="TState">The concrete State 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 TState : FsmStateBase<TState, TCtx>
|
||||
where TCtx : FsmContextBase<TCtx>
|
||||
@ -69,7 +69,7 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
|
||||
}
|
||||
|
||||
// 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>;
|
||||
Context = o!.Context;
|
||||
@ -78,7 +78,7 @@ public abstract record class FsmBase<TSelf, TState, TCtx> : imm.Recorded<TSelf>
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public TSelf Transition(
|
||||
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 (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
|
||||
// virtual dispatch will call the 'Timed' override automatically.
|
||||
return Process(
|
||||
|
||||
373
imm/Imm.cs
373
imm/Imm.cs
@ -1,366 +1,30 @@
|
||||
#nullable enable
|
||||
//#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace imm;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the base interface for versioned, immutable objects.
|
||||
/// Provides access to metadata and potentially the previous version.
|
||||
/// Helper static class for processing immutable objects using a 'ref' pattern.
|
||||
/// Provides different levels of processing based on the type.
|
||||
/// </summary>
|
||||
public interface Obj
|
||||
public static class imm
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the base metadata associated with this version.
|
||||
/// Processes a 'Versioned' object (Level 1).
|
||||
/// </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(
|
||||
public static T LightProcess<T>(
|
||||
ref T obj,
|
||||
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
|
||||
string reason = "Processed" )
|
||||
where T : io.Versioned<T>
|
||||
{
|
||||
/// <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
|
||||
|
||||
obj = obj.Process( fn, reason );
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// --- Metadata Hierarchy ---
|
||||
|
||||
public interface VersionedMeta
|
||||
{
|
||||
public uint Version { get; }
|
||||
public string Reason { get; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obj metadata for version tracking.
|
||||
/// Processes a 'Recorded' object (Level 2), capturing caller info.
|
||||
/// </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,
|
||||
@ -368,11 +32,20 @@ public static class TimedExt
|
||||
[CallerMemberName] string dbgName = "",
|
||||
[CallerFilePath] string dbgPath = "",
|
||||
[CallerLineNumber] int dbgLine = 0,
|
||||
[CallerArgumentExpression( "fn" )] string dbgExpression = ""
|
||||
) where T : imm.Timed<T>
|
||||
[CallerArgumentExpression( "fn" )] string dbgExpression = "" )
|
||||
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 );
|
||||
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.
|
||||
}
|
||||
*/
|
||||
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace imm;
|
||||
namespace io;
|
||||
|
||||
/// <summary>
|
||||
/// An immutable list implementation that tracks history, metadata, and time.
|
||||
|
||||
@ -1,4 +1,2 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace imm;
|
||||
|
||||
378
imm/io.cs
Normal file
378
imm/io.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
*/
|
||||
52
imm/iu.cs
52
imm/iu.cs
@ -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.
|
||||
}
|
||||
@ -17,7 +17,7 @@ using System.Collections.Immutable;
|
||||
|
||||
namespace ser;
|
||||
|
||||
public record CodeGenConfig : imm.Recorded<CodeGenConfig>
|
||||
public record CodeGenConfig : io.Recorded<CodeGenConfig>
|
||||
{
|
||||
// Whitelists (if needed, otherwise rely on attributes/defaults)
|
||||
public ImmutableDictionary<string, ImmutableList<string>> WLProps { get; init; } = ImmutableDictionary<string, ImmutableList<string>>.Empty;
|
||||
|
||||
@ -490,7 +490,7 @@ namespace lib
|
||||
{
|
||||
if( _cfg.VerboseLogging ) log.info( $"" );
|
||||
|
||||
var isImm = typeof(imm.Obj).IsAssignableFrom( narrowType );
|
||||
var isImm = typeof(io.Obj).IsAssignableFrom( narrowType );
|
||||
|
||||
XmlNodeList allChildren = elem.ChildNodes;
|
||||
|
||||
@ -648,8 +648,8 @@ namespace lib
|
||||
}
|
||||
else
|
||||
{
|
||||
var imm = obj as imm.Obj;
|
||||
var newObj = imm.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" );
|
||||
var immObj = obj as io.Obj;
|
||||
var newObj = immObj.Record( $"From XML {fromStr}:{elem.ParentNode?.Name}{elem.Name}" );
|
||||
return newObj;
|
||||
}
|
||||
|
||||
@ -1290,7 +1290,7 @@ namespace lib
|
||||
HashSet<string> whitelistFields, 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 )
|
||||
{
|
||||
|
||||
@ -32,6 +32,7 @@ N O T D O I N G :
|
||||
|
||||
*/
|
||||
|
||||
#region Helpers
|
||||
public record struct Value<T>( T _val, string _exp = "" )
|
||||
{
|
||||
public static T Default = default!;
|
||||
@ -87,13 +88,10 @@ public struct SourceLoc
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endregion // Helpers
|
||||
|
||||
static public class log
|
||||
{
|
||||
|
||||
//static
|
||||
|
||||
#region CLR Logging
|
||||
|
||||
|
||||
@ -155,14 +153,6 @@ static public class log
|
||||
|
||||
#endregion // CLR Logging
|
||||
|
||||
static public Value<T> Value<T>( T val,
|
||||
[CallerArgumentExpression("val")]
|
||||
string dbgExp = ""
|
||||
)
|
||||
{
|
||||
return new( val, dbgExp );
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LogType
|
||||
{
|
||||
@ -191,6 +181,8 @@ static public class log
|
||||
}
|
||||
|
||||
|
||||
#region LogEvent
|
||||
|
||||
public struct LogEvent
|
||||
{
|
||||
public DateTime Time;
|
||||
@ -264,7 +256,7 @@ static public class log
|
||||
ImmutableInterlocked.AddOrUpdate( ref s_logEPforCat, cat, ep, ( k, v ) => ep );
|
||||
}
|
||||
|
||||
|
||||
#endregion // LogEvent
|
||||
|
||||
static public void shutdown()
|
||||
{
|
||||
@ -1020,4 +1012,13 @@ static public class log
|
||||
|
||||
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
|
||||
|
||||
@ -182,7 +182,7 @@ public class Ref<T> : Ref where T : class, new()
|
||||
// Let's assume you'll add saving logic here.
|
||||
// Mgr.Save(value, path); // Example: Needs implementation
|
||||
|
||||
var immMeta = (value as imm.Obj)?.Meta;
|
||||
var immMeta = (value as io.Obj)?.Meta;
|
||||
|
||||
|
||||
|
||||
@ -433,10 +433,10 @@ public static class Mgr
|
||||
var loadedObject = loaderHolder.Load( filename, reason, dbgName, dbgPath, dbgLine );
|
||||
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( value is imm.Obj imm )
|
||||
if( value is io.Obj imm )
|
||||
{
|
||||
return (T)imm.Record( $"Loading bcs {reason}", dbgName, dbgPath, dbgLine );
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ public enum BackingFieldNaming { Short, Regular }
|
||||
public enum POD { Attributes, Elements }
|
||||
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 Datastructure Structure { get; init; } = Datastructure.Tree;
|
||||
@ -254,7 +254,7 @@ public class TypeMetaCache
|
||||
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 serTypes = typesAtt?.Types ?? ser.Types.None;
|
||||
|
||||
@ -179,7 +179,7 @@ public partial class ObjectHandler : ITypeHandler
|
||||
// 3. Post-processing
|
||||
if( obj is ser.I_Serialize iSer )
|
||||
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 obj;
|
||||
|
||||
@ -9,7 +9,7 @@ using System.Text;
|
||||
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
|
||||
{
|
||||
|
||||
@ -10,7 +10,7 @@ using System.IO;
|
||||
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
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user