< Summary

Information
Class: Trailblazer.Support.TransientStateUtility
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Support/TransientState/TransientStateUtility.cs
Line coverage
100%
Covered lines: 57
Uncovered lines: 0
Coverable lines: 57
Total lines: 119
Line coverage: 100%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
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%22100%
Clear(...)100%11100%
BuildSyncDelegate(...)100%44100%
BuildClearDelegate(...)100%44100%
GetDefaultExpression(...)100%44100%
GetStaticMemberExpression(...)100%44100%
GetTransientProperties(...)100%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// TransientStateUtility.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using System;
 9using System.Collections.Concurrent;
 10using System.Linq;
 11using System.Linq.Expressions;
 12using System.Reflection;
 13
 14namespace Trailblazer.Support;
 15
 16/// <summary>
 17/// Shared implementation for transient-state synchronization and reset behavior.
 18/// </summary>
 19/// <remarks>
 20/// Delegates are compiled once per type on first use via expression trees, so per-call overhead
 21/// is direct property assignment with no reflection at runtime.
 22/// </remarks>
 23internal static class TransientStateUtility
 24{
 125    private static readonly ConcurrentDictionary<Type, Action<ITransient, ITransient>> _syncDelegates = new();
 126    private static readonly ConcurrentDictionary<Type, Action<ITransient>> _clearDelegates = new();
 27
 28    internal static void Sync(ITransient instance, ITransient other)
 29    {
 730        SwiftThrowHelper.ThrowIfNull(other, nameof(other));
 31
 632        Type sourceType = instance.GetType();
 633        Type targetType = other.GetType();
 634        SwiftThrowHelper.ThrowIfArgument(
 635            sourceType != targetType,
 636            paramName: nameof(other),
 637            message: $"Type mismatch during SyncTransientState. Expected {sourceType.FullName}, but received {targetType
 38
 539        _syncDelegates.GetOrAdd(sourceType, BuildSyncDelegate)(instance, other);
 540    }
 41
 42    internal static void Clear(ITransient instance)
 43    {
 83744        _clearDelegates.GetOrAdd(instance.GetType(), BuildClearDelegate)(instance);
 83645    }
 46
 47    private static Action<ITransient, ITransient> BuildSyncDelegate(Type type)
 48    {
 549        PropertyInfo[] properties = GetTransientProperties(type);
 550        if (properties.Length == 0)
 151            return static (_, _) => { };
 52
 453        ParameterExpression instanceParam = Expression.Parameter(typeof(ITransient), "instance");
 454        ParameterExpression otherParam = Expression.Parameter(typeof(ITransient), "other");
 455        ParameterExpression typedInstance = Expression.Variable(type, "typedInstance");
 456        ParameterExpression typedOther = Expression.Variable(type, "typedOther");
 57
 458        Expression[] body = new Expression[2 + properties.Length];
 459        body[0] = Expression.Assign(typedInstance, Expression.Convert(instanceParam, type));
 460        body[1] = Expression.Assign(typedOther, Expression.Convert(otherParam, type));
 4461        for (int i = 0; i < properties.Length; i++)
 1862            body[i + 2] = Expression.Assign(
 1863                Expression.Property(typedInstance, properties[i]),
 1864                Expression.Property(typedOther, properties[i]));
 65
 466        BlockExpression block = Expression.Block(new[] { typedInstance, typedOther }, body);
 467        return Expression.Lambda<Action<ITransient, ITransient>>(block, instanceParam, otherParam).Compile();
 68    }
 69
 70    private static Action<ITransient> BuildClearDelegate(Type type)
 71    {
 1372        PropertyInfo[] properties = GetTransientProperties(type);
 1373        if (properties.Length == 0)
 174            return static _ => { };
 75
 1276        ParameterExpression instanceParam = Expression.Parameter(typeof(ITransient), "instance");
 1277        ParameterExpression typedVar = Expression.Variable(type, "typed");
 78
 1279        Expression[] body = new Expression[1 + properties.Length];
 1280        body[0] = Expression.Assign(typedVar, Expression.Convert(instanceParam, type));
 11481        for (int i = 0; i < properties.Length; i++)
 4682            body[i + 1] = Expression.Assign(
 4683                Expression.Property(typedVar, properties[i]),
 4684                GetDefaultExpression(properties[i]));
 85
 1186        BlockExpression block = Expression.Block(new[] { typedVar }, body);
 1187        return Expression.Lambda<Action<ITransient>>(block, instanceParam).Compile();
 88    }
 89
 90    private static Expression GetDefaultExpression(PropertyInfo property)
 91    {
 4692        TransientAttribute attr = property.GetCustomAttribute<TransientAttribute>()!;
 4693        if (attr.DefaultValueSource != null && attr.DefaultValueMember != null)
 594            return GetStaticMemberExpression(attr.DefaultValueSource, attr.DefaultValueMember);
 95
 4196        return Expression.Default(property.PropertyType);
 97    }
 98
 99    private static Expression GetStaticMemberExpression(Type type, string memberName)
 100    {
 5101        FieldInfo? field = type.GetField(memberName, BindingFlags.Public | BindingFlags.Static);
 5102        if (field != null)
 1103            return Expression.Field(null, field);
 104
 4105        PropertyInfo? prop = type.GetProperty(memberName, BindingFlags.Public | BindingFlags.Static);
 4106        SwiftThrowHelper.ThrowIfTrue(
 4107            prop == null,
 4108            message: $"Transient default member '{type.FullName}.{memberName}' was not found.");
 109
 3110        return Expression.Property(null, prop);
 111    }
 112
 113    private static PropertyInfo[] GetTransientProperties(Type type)
 114    {
 18115        return type.GetProperties()
 18116            .Where(static p => p.IsDefined(typeof(TransientAttribute), false))
 18117            .ToArray();
 118    }
 119}