< Summary

Information
Class: ArturRios.Data.Export.Abstractions.ColumnMap
Assembly: ArturRios.Data.Export
File(s): D:\Repositories\dotnet-data\src\ArturRios.Data.Export\Abstractions\ColumnMap.cs
Line coverage
100%
Covered lines: 30
Uncovered lines: 0
Coverable lines: 30
Total lines: 63
Line coverage: 100%
Branch coverage
80%
Covered branches: 8
Total branches: 10
Branch coverage: 80%
Method coverage
100%
Covered methods: 5
Fully covered methods: 3
Total methods: 5
Method coverage: 100%
Full method coverage: 60%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
For()100%11100%
For(...)100%11100%
Build(...)80%1010100%
BuildGetter(...)100%11100%

File(s)

D:\Repositories\dotnet-data\src\ArturRios.Data.Export\Abstractions\ColumnMap.cs

#LineLine coverage
 1using System.Collections.Concurrent;
 2using System.Linq.Expressions;
 3using System.Reflection;
 4using ArturRios.Data.Export.Attributes;
 5
 6namespace ArturRios.Data.Export.Abstractions;
 7
 8/// <summary>A single export column: a header and a compiled getter over the record.</summary>
 9public sealed class Column(string header, Func<object, object?> getter)
 10{
 11    /// <summary>The column header.</summary>
 12    public string Header { get; } = header;
 13
 14    /// <summary>Reads the column value from a record instance.</summary>
 15    public Func<object, object?> Getter { get; } = getter;
 16}
 17
 18/// <summary>Builds and caches the ordered column plan for a record type.</summary>
 19public static class ColumnMap
 20{
 121    private static readonly ConcurrentDictionary<Type, IReadOnlyList<Column>> Cache = new();
 22
 23    /// <summary>Returns the cached column plan for <typeparamref name="T" />.</summary>
 1524    public static IReadOnlyList<Column> For<T>() => For(typeof(T));
 25
 26    /// <summary>Returns the cached column plan for <paramref name="type" />.</summary>
 1527    public static IReadOnlyList<Column> For(Type type) => Cache.GetOrAdd(type, Build);
 28
 29    private static IReadOnlyList<Column> Build(Type type)
 430    {
 431        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
 1632            .Where(p => p.CanRead && p.GetIndexParameters().Length == 0
 1633                        && p.GetCustomAttribute<ExportIgnoreAttribute>() is null);
 34
 435        var planned = properties.Select(p =>
 1136        {
 1137            var attribute = p.GetCustomAttribute<ExportColumnAttribute>();
 1138            return new
 1139            {
 1140                Header = attribute?.Name ?? p.Name,
 1141                Order = attribute?.Order ?? int.MaxValue,
 1142                Token = p.MetadataToken,
 1143                Getter = BuildGetter(p)
 1144            };
 1545        });
 46
 447        return planned
 3348            .OrderBy(x => x.Order).ThenBy(x => x.Token).ThenBy(x => x.Header, StringComparer.Ordinal)
 1149            .Select(x => new Column(x.Header, x.Getter))
 450            .ToArray();
 451    }
 52
 53    // Compiles a delegate getter once per property (cached with the column plan) to avoid per-row reflection.
 54    private static Func<object, object?> BuildGetter(PropertyInfo property)
 1155    {
 1156        var instance = Expression.Parameter(typeof(object), "instance");
 1157        var typed = Expression.Convert(instance, property.DeclaringType!);
 1158        var access = Expression.Property(typed, property);
 1159        var boxed = Expression.Convert(access, typeof(object));
 60
 1161        return Expression.Lambda<Func<object, object?>>(boxed, instance).Compile();
 1162    }
 63}