< Summary

Information
Class: Trailblazer.Support.TransientStateUtility
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Support/TransientState/TransientStateUtility.cs
Line coverage
96%
Covered lines: 56
Uncovered lines: 2
Coverable lines: 58
Total lines: 115
Line coverage: 96.5%
Branch coverage
90%
Covered branches: 20
Total branches: 22
Branch coverage: 90.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Sync(...)100%44100%
Clear(...)100%11100%
BuildSyncDelegate(...)100%44100%
BuildClearDelegate(...)100%44100%
GetDefaultExpression(...)83.33%66100%
GetStaticMemberExpression(...)75%4475%
GetTransientProperties(...)100%11100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Support/TransientState/TransientStateUtility.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Linq;
 4using System.Linq.Expressions;
 5using System.Reflection;
 6
 7namespace Trailblazer.Support;
 8
 9/// <summary>
 10/// Shared implementation for transient-state synchronization and reset behavior.
 11/// </summary>
 12/// <remarks>
 13/// Delegates are compiled once per type on first use via expression trees, so per-call overhead
 14/// is direct property assignment with no reflection at runtime.
 15/// </remarks>
 16internal static class TransientStateUtility
 17{
 118    private static readonly ConcurrentDictionary<Type, Action<ITransient, ITransient>> _syncDelegates = new();
 119    private static readonly ConcurrentDictionary<Type, Action<ITransient>> _clearDelegates = new();
 20
 21    internal static void Sync(ITransient instance, ITransient other)
 22    {
 723        if (other == null)
 124            throw new ArgumentNullException(nameof(other));
 25
 626        Type sourceType = instance.GetType();
 627        Type targetType = other.GetType();
 628        if (sourceType != targetType)
 29        {
 130            throw new ArgumentException(
 131                $"Type mismatch during SyncTransientState. Expected {sourceType.FullName}, but received {targetType.Full
 132                nameof(other));
 33        }
 34
 535        _syncDelegates.GetOrAdd(sourceType, BuildSyncDelegate)(instance, other);
 536    }
 37
 38    internal static void Clear(ITransient instance)
 39    {
 59640        _clearDelegates.GetOrAdd(instance.GetType(), BuildClearDelegate)(instance);
 59641    }
 42
 43    private static Action<ITransient, ITransient> BuildSyncDelegate(Type type)
 44    {
 545        PropertyInfo[] properties = GetTransientProperties(type);
 546        if (properties.Length == 0)
 147            return static (_, _) => { };
 48
 449        ParameterExpression instanceParam = Expression.Parameter(typeof(ITransient), "instance");
 450        ParameterExpression otherParam = Expression.Parameter(typeof(ITransient), "other");
 451        ParameterExpression typedInstance = Expression.Variable(type, "typedInstance");
 452        ParameterExpression typedOther = Expression.Variable(type, "typedOther");
 53
 454        Expression[] body = new Expression[2 + properties.Length];
 455        body[0] = Expression.Assign(typedInstance, Expression.Convert(instanceParam, type));
 456        body[1] = Expression.Assign(typedOther, Expression.Convert(otherParam, type));
 4457        for (int i = 0; i < properties.Length; i++)
 1858            body[i + 2] = Expression.Assign(
 1859                Expression.Property(typedInstance, properties[i]),
 1860                Expression.Property(typedOther, properties[i]));
 61
 462        BlockExpression block = Expression.Block(new[] { typedInstance, typedOther }, body);
 463        return Expression.Lambda<Action<ITransient, ITransient>>(block, instanceParam, otherParam).Compile();
 64    }
 65
 66    private static Action<ITransient> BuildClearDelegate(Type type)
 67    {
 1168        PropertyInfo[] properties = GetTransientProperties(type);
 1169        if (properties.Length == 0)
 170            return static _ => { };
 71
 1072        ParameterExpression instanceParam = Expression.Parameter(typeof(ITransient), "instance");
 1073        ParameterExpression typedVar = Expression.Variable(type, "typed");
 74
 1075        Expression[] body = new Expression[1 + properties.Length];
 1076        body[0] = Expression.Assign(typedVar, Expression.Convert(instanceParam, type));
 10877        for (int i = 0; i < properties.Length; i++)
 4478            body[i + 1] = Expression.Assign(
 4479                Expression.Property(typedVar, properties[i]),
 4480                GetDefaultExpression(properties[i]));
 81
 1082        BlockExpression block = Expression.Block(new[] { typedVar }, body);
 1083        return Expression.Lambda<Action<ITransient>>(block, instanceParam).Compile();
 84    }
 85
 86    private static Expression GetDefaultExpression(PropertyInfo property)
 87    {
 4488        TransientAttribute? attr = property.GetCustomAttribute<TransientAttribute>();
 4489        if (attr?.DefaultValueSource != null && attr.DefaultValueMember != null)
 490            return GetStaticMemberExpression(attr.DefaultValueSource, attr.DefaultValueMember);
 91
 4092        return Expression.Default(property.PropertyType);
 93    }
 94
 95    private static Expression GetStaticMemberExpression(Type type, string memberName)
 96    {
 497        FieldInfo? field = type.GetField(memberName, BindingFlags.Public | BindingFlags.Static);
 498        if (field != null)
 399            return Expression.Field(null, field);
 100
 1101        PropertyInfo? prop = type.GetProperty(memberName, BindingFlags.Public | BindingFlags.Static);
 1102        if (prop == null)
 0103            throw new InvalidOperationException(
 0104                $"Transient default member '{type.FullName}.{memberName}' was not found.");
 105
 1106        return Expression.Property(null, prop);
 107    }
 108
 109    private static PropertyInfo[] GetTransientProperties(Type type)
 110    {
 16111        return type.GetProperties()
 16112            .Where(static p => p.IsDefined(typeof(TransientAttribute), false))
 16113            .ToArray();
 114    }
 115}