• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

loresoft / FluentCommand / 26594173245

28 May 2026 06:28PM UTC coverage: 55.553% (+0.7%) from 54.902%
26594173245

push

github

pwelter34
Move JSON support, add docs and examples

1358 of 3215 branches covered (42.24%)

Branch coverage included in aggregate %.

103 of 234 new or added lines in 9 files covered. (44.02%)

371 existing lines in 26 files now uncovered.

4389 of 7130 relevant lines covered (61.56%)

312.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.39
/src/FluentCommand/Reflection/TypeAccessor.cs
1
using System.Collections.Concurrent;
2
using System.ComponentModel.DataAnnotations.Schema;
3
using System.Linq.Expressions;
4
using System.Reflection;
5

6
namespace FluentCommand.Reflection;
7

8
/// <summary>
9
/// Provides efficient access to type reflection information, including dynamic creation, property, field, and method accessors,
10
/// and metadata such as table mapping. Caches accessors for performance and supports late-bound operations on types.
11
/// </summary>
12
public class TypeAccessor
13
{
14
    private static readonly ConcurrentDictionary<Type, TypeAccessor> _typeCache = new();
4✔
15
    private readonly ConcurrentDictionary<string, IMemberAccessor?> _memberCache = new();
88✔
16
    private readonly ConcurrentDictionary<int, IMethodAccessor?> _methodCache = new();
88✔
17
    private readonly ConcurrentDictionary<int, IMemberAccessor[]> _propertyCache = new();
88✔
18

19
    private readonly Lazy<Func<object>?> _constructor;
20
    private readonly Lazy<TableAttribute?> _tableAttribute;
21

22
    private readonly string? _tableName;
23
    private readonly string? _tableSchema;
24

25
    /// <summary>
26
    /// Initializes a new instance of the <see cref="TypeAccessor"/> class for the specified <see cref="Type"/>.
27
    /// </summary>
28
    /// <param name="type">The <see cref="Type"/> this accessor is for.</param>
29
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="type"/> is <c>null</c>.</exception>
30
    public TypeAccessor(Type type)
4✔
31
    {
32
        ArgumentNullException.ThrowIfNull(type);
4✔
33

34
        Type = type;
4✔
35
        _constructor = new Lazy<Func<object>?>(() => ExpressionFactory.CreateConstructor(Type));
4✔
36
        _tableAttribute = new Lazy<TableAttribute?>(() => type.GetCustomAttribute<TableAttribute>(true));
4✔
37
    }
4✔
38

39
    /// <summary>
40
    /// Initializes a new instance of the <see cref="TypeAccessor"/> class with pre-generated metadata,
41
    /// avoiding runtime reflection and expression compilation for AOT-friendly scenarios.
42
    /// </summary>
43
    /// <param name="type">The <see cref="Type"/> this accessor is for.</param>
44
    /// <param name="tableName">The pre-computed table name, or <c>null</c> to fall back to the type name.</param>
45
    /// <param name="tableSchema">The pre-computed table schema, or <c>null</c>.</param>
46
    /// <param name="constructor">A delegate that creates a new instance of the type, or <c>null</c> if no parameterless constructor exists.</param>
47
    protected TypeAccessor(
84✔
48
        Type type,
84✔
49
        string? tableName,
84✔
50
        string? tableSchema,
84✔
51
        Func<object>? constructor)
84✔
52
    {
53
        ArgumentNullException.ThrowIfNull(type);
84✔
54

55
        Type = type;
84✔
56

57
        _tableName = tableName;
84✔
58
        _tableSchema = tableSchema;
84✔
59

60
#if NET6_0_OR_GREATER
61
        _constructor = new Lazy<Func<object>?>(constructor);
84✔
62
        _tableAttribute = new Lazy<TableAttribute?>((TableAttribute?)null);
84✔
63
#else
64
        _constructor = new Lazy<Func<object>?>(() => constructor);
65
        _tableAttribute = new Lazy<TableAttribute?>(static () => null);
66
#endif
67
    }
84✔
68

69
    /// <summary>
70
    /// Gets the <see cref="Type"/> this accessor is for.
71
    /// </summary>
72
    /// <value>The <see cref="Type"/> this accessor is for.</value>
73
    public Type Type { get; }
74

75
    /// <summary>
76
    /// Gets the name of the type.
77
    /// </summary>
78
    /// <value>The name of the type.</value>
79
    public string Name => Type.Name;
6✔
80

81
    /// <summary>
82
    /// Gets the name of the table the class is mapped to, as specified by the <see cref="TableAttribute"/>.
83
    /// If not specified, returns the type name.
84
    /// </summary>
85
    /// <value>The name of the mapped table.</value>
86
    public string TableName => _tableName ?? _tableAttribute.Value?.Name ?? Type.Name;
130✔
87

88
    /// <summary>
89
    /// Gets the schema of the table the class is mapped to, as specified by the <see cref="TableAttribute"/>.
90
    /// </summary>
91
    /// <value>The schema of the mapped table, or <c>null</c> if not specified.</value>
92
    public string? TableSchema => _tableSchema ?? _tableAttribute.Value?.Schema;
159✔
93

94
    /// <summary>
95
    /// Creates a new instance of the type represented by this accessor using the default constructor.
96
    /// </summary>
97
    /// <returns>A new instance of the type.</returns>
98
    /// <exception cref="InvalidOperationException">Thrown if a parameterless constructor is not found.</exception>
99
    public object Create()
100
    {
101
        var constructor = _constructor.Value;
1✔
102
        if (constructor == null)
1!
UNCOV
103
            throw new InvalidOperationException($"Could not find constructor for '{Type.Name}'.");
×
104

105
        return constructor.Invoke();
1✔
106
    }
107

108
    #region Method
109

110
    /// <summary>
111
    /// Finds a method with the specified <paramref name="name"/> and no parameters.
112
    /// </summary>
113
    /// <param name="name">The name of the method.</param>
114
    /// <returns>An <see cref="IMethodAccessor"/> for the method, or <c>null</c> if not found.</returns>
115
    public IMethodAccessor? FindMethod(string name)
116
    {
UNCOV
117
        return FindMethod(name, Type.EmptyTypes);
×
118
    }
119

120
    /// <summary>
121
    /// Finds a method with the specified <paramref name="name"/> and parameter types.
122
    /// </summary>
123
    /// <param name="name">The name of the method.</param>
124
    /// <param name="parameterTypes">The method parameter types.</param>
125
    /// <returns>An <see cref="IMethodAccessor"/> for the method, or <c>null</c> if not found.</returns>
126
    public IMethodAccessor? FindMethod(string name, params Type[] parameterTypes)
127
    {
UNCOV
128
        return FindMethod(name, parameterTypes, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
×
129
    }
130

131
    /// <summary>
132
    /// Finds a method with the specified <paramref name="name"/>, parameter types, and binding flags.
133
    /// </summary>
134
    /// <param name="name">The name of the method.</param>
135
    /// <param name="parameterTypes">The method parameter types.</param>
136
    /// <param name="flags">The binding flags to use for the search.</param>
137
    /// <returns>An <see cref="IMethodAccessor"/> for the method, or <c>null</c> if not found.</returns>
138
    public IMethodAccessor? FindMethod(string name, Type[] parameterTypes, BindingFlags flags)
139
    {
UNCOV
140
        int key = MethodAccessor.GetKey(name, parameterTypes);
×
141

142
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
UNCOV
143
        if (_methodCache.TryGetValue(key, out var cached))
×
UNCOV
144
            return cached;
×
145

146
        return _methodCache.GetOrAdd(key, n => CreateMethodAccessor(name, parameterTypes, flags));
×
147
    }
148

149
    private MethodAccessor? CreateMethodAccessor(string name, Type[] parameters, BindingFlags flags)
150
    {
UNCOV
151
        var info = FindMethod(Type, name, parameters, flags);
×
UNCOV
152
        return info == null ? null : CreateAccessor(info);
×
153
    }
154

155
    private static MethodInfo? FindMethod(Type type, string name, Type[] parameterTypes, BindingFlags flags)
156
    {
UNCOV
157
        ArgumentNullException.ThrowIfNull(type);
×
UNCOV
158
        ArgumentNullException.ThrowIfNull(name);
×
159

160
        if (parameterTypes == null)
×
161
            parameterTypes = Type.EmptyTypes;
×
162

UNCOV
163
        var typeInfo = type.GetTypeInfo();
×
164

165
        //first try full match
UNCOV
166
        var methodInfo = typeInfo.GetMethod(name, parameterTypes);
×
167
        if (methodInfo != null)
×
UNCOV
168
            return methodInfo;
×
169

170
        // next, get all that match by name
171
        var methodsByName = typeInfo.GetMethods(flags)
×
172
          .Where(m => m.Name == name)
×
UNCOV
173
          .ToList();
×
174

175
        if (methodsByName.Count == 0)
×
176
            return null;
×
177

178
        // if only one matches name, return it
179
        if (methodsByName.Count == 1)
×
180
            return methodsByName[0];
×
181

182
        // next, get all methods that match param count
183
        var methodsByParamCount = methodsByName
×
184
            .Where(m => m.GetParameters().Length == parameterTypes.Length)
×
UNCOV
185
            .ToList();
×
186

187
        // if only one matches with same param count, return it
188
        if (methodsByParamCount.Count == 1)
×
189
            return methodsByParamCount[0];
×
190

191
        // still no match, make best guess by greatest matching param types
192
        MethodInfo? current = methodsByParamCount.FirstOrDefault();
×
193
        int matchCount = 0;
×
194

UNCOV
195
        foreach (var info in methodsByParamCount)
×
196
        {
197
            var paramTypes = info.GetParameters()
×
UNCOV
198
                .Select(static p => p.ParameterType)
×
199
                .ToArray();
×
200

201
            // unsure which way IsAssignableFrom should be checked?
202
            int count = paramTypes
×
203
                .Select(static t => t.GetTypeInfo())
×
UNCOV
204
                .Where((t, i) => t.IsAssignableFrom(parameterTypes[i]))
×
UNCOV
205
                .Count();
×
206

207
            if (count <= matchCount)
×
208
                continue;
209

UNCOV
210
            current = info;
×
211
            matchCount = count;
×
212
        }
213

214
        return current;
×
215
    }
216

217
    private static MethodAccessor? CreateAccessor(MethodInfo methodInfo)
218
    {
UNCOV
219
        return methodInfo == null ? null : new MethodAccessor(methodInfo);
×
220
    }
221
    #endregion
222

223
    #region Find
224

225
    /// <summary>
226
    /// Searches for the public property or field with the specified name.
227
    /// </summary>
228
    /// <param name="name">The name of the property or field to find.</param>
229
    /// <returns>An <see cref="IMemberAccessor"/> for the property or field if found; otherwise, <c>null</c>.</returns>
230
    public IMemberAccessor? Find(string name)
231
    {
232
        return Find(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
20✔
233
    }
234

235
    /// <summary>
236
    /// Searches for the specified property or field using the specified binding flags.
237
    /// </summary>
238
    /// <param name="name">The name of the property or field to find.</param>
239
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
240
    /// <returns>An <see cref="IMemberAccessor"/> for the property or field if found; otherwise, <c>null</c>.</returns>
241
    public IMemberAccessor? Find(string name, BindingFlags flags)
242
    {
243
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
244
        if (_memberCache.TryGetValue(name, out var cached))
20!
245
            return cached;
20✔
246

UNCOV
247
        return _memberCache.GetOrAdd(name, n => CreateAccessor(n, flags));
×
248
    }
249

250
    private IMemberAccessor? CreateAccessor(string name, BindingFlags flags)
251
    {
252
        // first try property
UNCOV
253
        var property = FindProperty(Type, name, flags);
×
UNCOV
254
        if (property != null)
×
UNCOV
255
            return CreateAccessor(property);
×
256

257
        // next try field
258
        var field = FindField(Type, name, flags);
×
259
        if (field != null)
×
UNCOV
260
            return CreateAccessor(field);
×
261

262
        return null;
×
263
    }
264
    #endregion
265

266
    #region Column
267
    /// <summary>
268
    /// Searches for the public property with the specified column name.
269
    /// </summary>
270
    /// <param name="name">The name of the property or field to find.</param>
271
    /// <returns>An <see cref="IMemberAccessor"/> instance for the property or field if found; otherwise <c>null</c>.</returns>
272
    public IMemberAccessor? FindColumn(string name)
273
    {
274
        return FindColumn(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
8✔
275
    }
276

277
    /// <summary>
278
    /// Searches for the property with the specified column name and binding flags, using <see cref="ColumnAttribute"/> if present.
279
    /// </summary>
280
    /// <param name="name">The name of the property or column to find.</param>
281
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
282
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
283
    public IMemberAccessor? FindColumn(string name, BindingFlags flags)
284
    {
285
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
286
        if (_memberCache.TryGetValue(name, out var cached))
8✔
287
            return cached;
7✔
288

289
        return _memberCache.GetOrAdd(name, n => CreateColumnAccessor(n, flags));
1✔
290
    }
291

292
    private IMemberAccessor? CreateColumnAccessor(string name, BindingFlags flags)
293
    {
294
        var typeInfo = Type.GetTypeInfo();
1✔
295

296
        foreach (var p in typeInfo.GetProperties(flags))
3!
297
        {
298
            // try ColumnAttribute
299
            var columnAttribute = p.GetCustomAttribute<System.ComponentModel.DataAnnotations.Schema.ColumnAttribute>();
1✔
300
            if (columnAttribute != null && name.Equals(columnAttribute.Name, StringComparison.OrdinalIgnoreCase))
1!
301
                return CreateAccessor(p);
1✔
302

UNCOV
303
            if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
×
UNCOV
304
                return CreateAccessor(p);
×
305
        }
306

307
        return null;
×
308
    }
309
    #endregion
310

311
    #region Property
312
    /// <summary>
313
    /// Searches for the property using a property expression.
314
    /// </summary>
315
    /// <typeparam name="T">The object type containing the property specified in the expression.</typeparam>
316
    /// <param name="propertyExpression">The property expression (e.g. <c>p =&gt; p.PropertyName</c>).</param>
317
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
318
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="propertyExpression"/> is <c>null</c>.</exception>
319
    /// <exception cref="ArgumentException">Thrown if the expression is not a valid property access expression.</exception>
320
    public IMemberAccessor? FindProperty<T>(Expression<Func<T>> propertyExpression)
321
    {
UNCOV
322
        ArgumentNullException.ThrowIfNull(propertyExpression);
×
323

UNCOV
324
        if (propertyExpression.Body is UnaryExpression unaryExpression)
×
UNCOV
325
            return FindProperty(unaryExpression.Operand as MemberExpression);
×
326
        else
327
            return FindProperty(propertyExpression.Body as MemberExpression);
×
328
    }
329

330
    /// <summary>
331
    /// Searches for the property using a property expression.
332
    /// </summary>
333
    /// <typeparam name="TSource">The object type containing the property specified in the expression.</typeparam>
334
    /// <typeparam name="TValue">The type of the value.</typeparam>
335
    /// <param name="propertyExpression">The property expression (e.g. <c>p =&gt; p.PropertyName</c>).</param>
336
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
337
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="propertyExpression"/> is <c>null</c>.</exception>
338
    /// <exception cref="ArgumentException">Thrown if the expression is not a valid property access expression.</exception>
339
    public IMemberAccessor? FindProperty<TSource, TValue>(Expression<Func<TSource, TValue>> propertyExpression)
340
    {
341
        ArgumentNullException.ThrowIfNull(propertyExpression);
217✔
342

343
        if (propertyExpression.Body is UnaryExpression unaryExpression)
217✔
344
            return FindProperty(unaryExpression.Operand as MemberExpression);
10✔
345
        else
346
            return FindProperty(propertyExpression.Body as MemberExpression);
207✔
347
    }
348

349
    /// <summary>
350
    /// Searches for the public property with the specified name.
351
    /// </summary>
352
    /// <param name="name">The name of the property to find.</param>
353
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
354
    public IMemberAccessor? FindProperty(string name)
355
    {
356
        return FindProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
218✔
357
    }
358

359
    /// <summary>
360
    /// Searches for the property with the specified name and binding flags.
361
    /// </summary>
362
    /// <param name="name">The name of the property to find.</param>
363
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
364
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
365
    public IMemberAccessor? FindProperty(string name, BindingFlags flags)
366
    {
367
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
368
        if (_memberCache.TryGetValue(name, out var cached))
218✔
369
            return cached;
216✔
370

371
        return _memberCache.GetOrAdd(name, n => CreatePropertyAccessor(n, flags));
2✔
372
    }
373

374
    /// <summary>
375
    /// Gets the property member accessors for the type.
376
    /// </summary>
377
    /// <returns>An <see cref="IEnumerable{IMemberAccessor}"/> for the type's properties.</returns>
378
    public IEnumerable<IMemberAccessor> GetProperties()
379
    {
380
        return GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
97✔
381
    }
382

383
    /// <summary>
384
    /// Gets the property member accessors for the type using the specified binding flags.
385
    /// </summary>
386
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
387
    /// <returns>An <see cref="IEnumerable{IMemberAccessor}"/> for the type's properties.</returns>
388
    public IEnumerable<IMemberAccessor> GetProperties(BindingFlags flags)
389
    {
390
        var key = (int)flags;
97✔
391

392
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
393
        if (_propertyCache.TryGetValue(key, out var cached))
97✔
394
            return cached;
94✔
395

396
        return _propertyCache.GetOrAdd(key, k =>
3✔
397
        {
3✔
398
            var typeInfo = Type.GetTypeInfo();
3✔
399
            var properties = typeInfo.GetProperties(flags);
3✔
400
            return properties.Select(GetAccessor).ToArray();
3✔
401
        });
3✔
402
    }
403

404

405
    private IMemberAccessor GetAccessor(PropertyInfo propertyInfo)
406
    {
407
        ArgumentNullException.ThrowIfNull(propertyInfo);
9✔
408

409
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
410
        if (_memberCache.TryGetValue(propertyInfo.Name, out var cached) && cached != null)
9!
UNCOV
411
            return cached;
×
412

413
        return _memberCache.GetOrAdd(propertyInfo.Name, n => CreateAccessor(propertyInfo))!;
9✔
414
    }
415

416
    private PropertyAccessor? CreatePropertyAccessor(string name, BindingFlags flags)
417
    {
418
        var info = FindProperty(Type, name, flags);
2✔
419
        return info == null ? null : CreateAccessor(info);
2!
420
    }
421

422
    private IMemberAccessor? FindProperty(MemberExpression? memberExpression)
423
    {
424
        if (memberExpression == null)
217!
UNCOV
425
            throw new ArgumentException("The expression is not a member access expression.", nameof(memberExpression));
×
426

427
        var property = memberExpression.Member as PropertyInfo;
217✔
428
        if (property == null)
217!
UNCOV
429
            throw new ArgumentException("The member access expression does not access a property.", nameof(memberExpression));
×
430

431
        // find by name because we can't trust the PropertyInfo here as it could be from an interface or inherited class
432
        return FindProperty(property.Name);
217✔
433
    }
434

435
    private static PropertyInfo? FindProperty(Type type, string name, BindingFlags flags)
436
    {
437
        ArgumentNullException.ThrowIfNull(type);
2✔
438

439
        ArgumentNullException.ThrowIfNull(name);
2✔
440

441
        var typeInfo = type.GetTypeInfo();
2✔
442
        // first try GetProperty
443
        var property = typeInfo.GetProperty(name, flags);
2✔
444
        if (property != null)
2!
445
            return property;
2✔
446

447
        // if not found, search while ignoring case
448
        return typeInfo
×
UNCOV
449
            .GetProperties(flags)
×
UNCOV
450
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
×
451
    }
452

453
    private static PropertyAccessor? CreateAccessor(PropertyInfo propertyInfo)
454
    {
455
        return propertyInfo == null ? null : new PropertyAccessor(propertyInfo);
12!
456
    }
457
    #endregion
458

459
    #region Field
460
    /// <summary>
461
    /// Searches for the specified field with the specified name.
462
    /// </summary>
463
    /// <param name="name">The name of the field to find.</param>
464
    /// <returns>
465
    /// An <see cref="IMemberAccessor"/> instance for the field if found; otherwise <c>null</c>.
466
    /// </returns>
467
    public IMemberAccessor? FindField(string name)
468
    {
UNCOV
469
        return FindField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
×
470
    }
471

472
    /// <summary>
473
    /// Searches for the field with the specified name and binding flags.
474
    /// </summary>
475
    /// <param name="name">The name of the field to find.</param>
476
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
477
    /// <returns>An <see cref="IMemberAccessor"/> for the field if found; otherwise, <c>null</c>.</returns>
478
    public IMemberAccessor? FindField(string name, BindingFlags flags)
479
    {
480
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
UNCOV
481
        if (_memberCache.TryGetValue(name, out var cached))
×
UNCOV
482
            return cached;
×
483

UNCOV
484
        return _memberCache.GetOrAdd(name, n => CreateFieldAccessor(n, flags));
×
485
    }
486

487
    private FieldAccessor? CreateFieldAccessor(string name, BindingFlags flags)
488
    {
UNCOV
489
        var info = FindField(Type, name, flags);
×
490
        return info == null ? null : CreateAccessor(info);
×
491
    }
492

493
    private static FieldInfo? FindField(Type type, string name, BindingFlags flags)
494
    {
UNCOV
495
        ArgumentNullException.ThrowIfNull(type);
×
496

UNCOV
497
        ArgumentNullException.ThrowIfNull(name);
×
498

499
        // first try GetField
UNCOV
500
        var typeInfo = type.GetTypeInfo();
×
UNCOV
501
        var field = typeInfo.GetField(name, flags);
×
UNCOV
502
        if (field != null)
×
UNCOV
503
            return field;
×
504

505
        // if not found, search while ignoring case
UNCOV
506
        return typeInfo
×
507
            .GetFields(flags)
×
508
            .FirstOrDefault(f => f.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
×
509
    }
510

511
    private static FieldAccessor? CreateAccessor(FieldInfo fieldInfo)
512
    {
513
        return fieldInfo == null ? null : new FieldAccessor(fieldInfo);
×
514
    }
515
    #endregion
516

517

518
    /// <summary>
519
    /// Gets the <see cref="TypeAccessor"/> for the specified Type.
520
    /// </summary>
521
    /// <typeparam name="T">The Type to get the accessor for.</typeparam>
522
    /// <returns></returns>
523
    public static TypeAccessor GetAccessor<T>()
524
    {
525
        return GetAccessor(typeof(T));
133✔
526
    }
527

528
    /// <summary>
529
    /// Gets the <see cref="TypeAccessor"/> for the specified <see cref="Type"/>.
530
    /// </summary>
531
    /// <param name="type">The type to get the accessor for.</param>
532
    /// <returns>The <see cref="TypeAccessor"/> for the specified type.</returns>
533
    public static TypeAccessor GetAccessor(Type type)
534
    {
535
        // fast-path: avoid overhead of GetOrAdd on cache hits
536
        if (_typeCache.TryGetValue(type, out var cached))
133✔
537
            return cached;
129✔
538

539
        return _typeCache.GetOrAdd(type, static t => new TypeAccessor(t));
4✔
540
    }
541

542

543
    /// <summary>
544
    /// Registers a pre-generated <see cref="TypeAccessor"/> for the specified <see cref="Type"/>,
545
    /// allowing source-generated metadata to be used instead of runtime reflection.
546
    /// </summary>
547
    /// <param name="type">The type to register the accessor for.</param>
548
    /// <param name="accessor">The pre-generated accessor.</param>
549
    public static void Register(Type type, TypeAccessor accessor)
550
    {
551
        ArgumentNullException.ThrowIfNull(type);
84✔
552
        ArgumentNullException.ThrowIfNull(accessor);
84✔
553
        _typeCache[type] = accessor;
84✔
554
    }
84✔
555

556

557
    /// <summary>
558
    /// Pre-registers a member accessor by name so that subsequent lookups
559
    /// via <see cref="Find(string)"/>, <see cref="FindProperty(string)"/>, or <see cref="FindColumn(string)"/>
560
    /// return it without reflection.
561
    /// </summary>
562
    /// <param name="name">The lookup key (property name or column name).</param>
563
    /// <param name="accessor">The member accessor to register.</param>
564
    protected void RegisterMember(string name, IMemberAccessor accessor)
565
    {
566
        _memberCache.TryAdd(name, accessor);
903✔
567
    }
903✔
568

569
    /// <summary>
570
    /// Pre-registers a collection of property accessors for the specified binding flags,
571
    /// so that <see cref="GetProperties(BindingFlags)"/> returns them without reflection.
572
    /// </summary>
573
    /// <param name="flags">The binding flags key.</param>
574
    /// <param name="properties">The property accessors to register.</param>
575
    protected void RegisterProperties(BindingFlags flags, IEnumerable<IMemberAccessor> properties)
576
    {
577
        _propertyCache.TryAdd((int)flags, [.. properties]);
84✔
578
    }
84✔
579
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc