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

loresoft / FluentCommand / 23278216331

19 Mar 2026 03:19AM UTC coverage: 57.398% (+0.7%) from 56.658%
23278216331

push

github

pwelter34
Enable nullable and improve source generators

1403 of 3069 branches covered (45.72%)

Branch coverage included in aggregate %.

527 of 907 new or added lines in 58 files covered. (58.1%)

22 existing lines in 10 files now uncovered.

4288 of 6846 relevant lines covered (62.64%)

330.58 hits per line

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

44.25
/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();
9✔
15
    private readonly ConcurrentDictionary<string, IMemberAccessor?> _memberCache = new();
139✔
16
    private readonly ConcurrentDictionary<int, IMethodAccessor?> _methodCache = new();
139✔
17
    private readonly ConcurrentDictionary<int, IMemberAccessor[]> _propertyCache = new();
139✔
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
        if (type == null)
4!
33
            throw new ArgumentNullException(nameof(type));
×
34

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

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

57
        Type = type;
135✔
58

59
        _tableName = tableName;
135✔
60
        _tableSchema = tableSchema;
135✔
61

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

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

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

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

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

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

107
        return constructor.Invoke();
1✔
108
    }
109

110
    #region Method
111

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

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

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

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

UNCOV
148
        return _methodCache.GetOrAdd(key, n => CreateMethodAccessor(name, parameterTypes, flags));
×
149
    }
150

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

157
    private static MethodInfo? FindMethod(Type type, string name, Type[] parameterTypes, BindingFlags flags)
158
    {
159
        if (type == null)
×
160
            throw new ArgumentNullException(nameof(type));
×
161
        if (name == null)
×
162
            throw new ArgumentNullException(nameof(name));
×
163

164
        if (parameterTypes == null)
×
165
            parameterTypes = Type.EmptyTypes;
×
166

167
        var typeInfo = type.GetTypeInfo();
×
168

169
        //first try full match
170
        var methodInfo = typeInfo.GetMethod(name, parameterTypes);
×
171
        if (methodInfo != null)
×
172
            return methodInfo;
×
173

174
        // next, get all that match by name
175
        var methodsByName = typeInfo.GetMethods(flags)
×
176
          .Where(m => m.Name == name)
×
177
          .ToList();
×
178

179
        if (methodsByName.Count == 0)
×
180
            return null;
×
181

182
        // if only one matches name, return it
183
        if (methodsByName.Count == 1)
×
NEW
184
            return methodsByName[0];
×
185

186
        // next, get all methods that match param count
187
        var methodsByParamCount = methodsByName
×
188
            .Where(m => m.GetParameters().Length == parameterTypes.Length)
×
189
            .ToList();
×
190

191
        // if only one matches with same param count, return it
192
        if (methodsByParamCount.Count == 1)
×
NEW
193
            return methodsByParamCount[0];
×
194

195
        // still no match, make best guess by greatest matching param types
NEW
196
        MethodInfo? current = methodsByParamCount.FirstOrDefault();
×
197
        int matchCount = 0;
×
198

199
        foreach (var info in methodsByParamCount)
×
200
        {
201
            var paramTypes = info.GetParameters()
×
NEW
202
                .Select(static p => p.ParameterType)
×
203
                .ToArray();
×
204

205
            // unsure which way IsAssignableFrom should be checked?
206
            int count = paramTypes
×
NEW
207
                .Select(static t => t.GetTypeInfo())
×
208
                .Where((t, i) => t.IsAssignableFrom(parameterTypes[i]))
×
209
                .Count();
×
210

211
            if (count <= matchCount)
×
212
                continue;
213

214
            current = info;
×
215
            matchCount = count;
×
216
        }
217

218
        return current;
×
219
    }
220

221
    private static MethodAccessor? CreateAccessor(MethodInfo methodInfo)
222
    {
223
        return methodInfo == null ? null : new MethodAccessor(methodInfo);
×
224
    }
225
    #endregion
226

227
    #region Find
228

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

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

UNCOV
251
        return _memberCache.GetOrAdd(name, n => CreateAccessor(n, flags));
×
252
    }
253

254
    private IMemberAccessor? CreateAccessor(string name, BindingFlags flags)
255
    {
256
        // first try property
257
        var property = FindProperty(Type, name, flags);
×
258
        if (property != null)
×
259
            return CreateAccessor(property);
×
260

261
        // next try field
262
        var field = FindField(Type, name, flags);
×
263
        if (field != null)
×
264
            return CreateAccessor(field);
×
265

266
        return null;
×
267
    }
268
    #endregion
269

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

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

293
        return _memberCache.GetOrAdd(name, n => CreateColumnAccessor(n, flags));
2✔
294
    }
295

296
    private IMemberAccessor? CreateColumnAccessor(string name, BindingFlags flags)
297
    {
298
        var typeInfo = Type.GetTypeInfo();
1✔
299

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

307
            if (p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
×
308
                return CreateAccessor(p);
×
309
        }
310

311
        return null;
×
312
    }
313
    #endregion
314

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

329
        if (propertyExpression.Body is UnaryExpression unaryExpression)
×
330
            return FindProperty(unaryExpression.Operand as MemberExpression);
×
331
        else
332
            return FindProperty(propertyExpression.Body as MemberExpression);
×
333
    }
334

335
    /// <summary>
336
    /// Searches for the property using a property expression.
337
    /// </summary>
338
    /// <typeparam name="TSource">The object type containing the property specified in the expression.</typeparam>
339
    /// <typeparam name="TValue">The type of the value.</typeparam>
340
    /// <param name="propertyExpression">The property expression (e.g. <c>p =&gt; p.PropertyName</c>).</param>
341
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
342
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="propertyExpression"/> is <c>null</c>.</exception>
343
    /// <exception cref="ArgumentException">Thrown if the expression is not a valid property access expression.</exception>
344
    public IMemberAccessor? FindProperty<TSource, TValue>(Expression<Func<TSource, TValue>> propertyExpression)
345
    {
346
        if (propertyExpression == null)
202!
347
            throw new ArgumentNullException(nameof(propertyExpression));
×
348

349
        if (propertyExpression.Body is UnaryExpression unaryExpression)
202✔
350
            return FindProperty(unaryExpression.Operand as MemberExpression);
10✔
351
        else
352
            return FindProperty(propertyExpression.Body as MemberExpression);
192✔
353
    }
354

355
    /// <summary>
356
    /// Searches for the public property with the specified name.
357
    /// </summary>
358
    /// <param name="name">The name of the property to find.</param>
359
    /// <returns>An <see cref="IMemberAccessor"/> for the property if found; otherwise, <c>null</c>.</returns>
360
    public IMemberAccessor? FindProperty(string name)
361
    {
362
        return FindProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
203✔
363
    }
364

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

377
        return _memberCache.GetOrAdd(name, n => CreatePropertyAccessor(n, flags));
4✔
378
    }
379

380
    /// <summary>
381
    /// Gets the property member accessors for the type.
382
    /// </summary>
383
    /// <returns>An <see cref="IEnumerable{IMemberAccessor}"/> for the type's properties.</returns>
384
    public IEnumerable<IMemberAccessor> GetProperties()
385
    {
386
        return GetProperties(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance);
58✔
387
    }
388

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

398
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
399
        if (_propertyCache.TryGetValue(key, out var cached))
58✔
400
            return cached;
55✔
401

402
        return _propertyCache.GetOrAdd(key, k =>
3✔
403
        {
3✔
404
            var typeInfo = Type.GetTypeInfo();
3✔
405
            var properties = typeInfo.GetProperties(flags);
3✔
406
            return properties.Select(GetAccessor).ToArray();
3✔
407
        });
3✔
408
    }
409

410

411
    private IMemberAccessor GetAccessor(PropertyInfo propertyInfo)
412
    {
413
        if (propertyInfo == null)
9!
414
            throw new ArgumentNullException(nameof(propertyInfo));
×
415

416
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
417
        if (_memberCache.TryGetValue(propertyInfo.Name, out var cached) && cached != null)
9!
NEW
418
            return cached;
×
419

420
        return _memberCache.GetOrAdd(propertyInfo.Name, n => CreateAccessor(propertyInfo))!;
18✔
421
    }
422

423
    private PropertyAccessor? CreatePropertyAccessor(string name, BindingFlags flags)
424
    {
425
        var info = FindProperty(Type, name, flags);
2✔
426
        return info == null ? null : CreateAccessor(info);
2!
427
    }
428

429
    private IMemberAccessor? FindProperty(MemberExpression? memberExpression)
430
    {
431
        if (memberExpression == null)
202!
432
            throw new ArgumentException("The expression is not a member access expression.", nameof(memberExpression));
×
433

434
        var property = memberExpression.Member as PropertyInfo;
202✔
435
        if (property == null)
202!
436
            throw new ArgumentException("The member access expression does not access a property.", nameof(memberExpression));
×
437

438
        // find by name because we can't trust the PropertyInfo here as it could be from an interface or inherited class
439
        return FindProperty(property.Name);
202✔
440
    }
441

442
    private static PropertyInfo? FindProperty(Type type, string name, BindingFlags flags)
443
    {
444
        if (type == null)
2!
445
            throw new ArgumentNullException(nameof(type));
×
446

447
        if (name == null)
2!
448
            throw new ArgumentNullException(nameof(name));
×
449

450
        var typeInfo = type.GetTypeInfo();
2✔
451
        // first try GetProperty
452
        var property = typeInfo.GetProperty(name, flags);
2✔
453
        if (property != null)
2!
454
            return property;
2✔
455

456
        // if not found, search while ignoring case
NEW
457
        return typeInfo
×
458
            .GetProperties(flags)
×
459
            .FirstOrDefault(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
×
460
    }
461

462
    private static PropertyAccessor? CreateAccessor(PropertyInfo propertyInfo)
463
    {
464
        return propertyInfo == null ? null : new PropertyAccessor(propertyInfo);
12!
465
    }
466
    #endregion
467

468
    #region Field
469
    /// <summary>
470
    /// Searches for the specified field with the specified name.
471
    /// </summary>
472
    /// <param name="name">The name of the field to find.</param>
473
    /// <returns>
474
    /// An <see cref="IMemberAccessor"/> instance for the field if found; otherwise <c>null</c>.
475
    /// </returns>
476
    public IMemberAccessor? FindField(string name)
477
    {
478
        return FindField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
×
479
    }
480

481
    /// <summary>
482
    /// Searches for the field with the specified name and binding flags.
483
    /// </summary>
484
    /// <param name="name">The name of the field to find.</param>
485
    /// <param name="flags">A bitmask comprised of one or more <see cref="BindingFlags"/> that specify how the search is conducted.</param>
486
    /// <returns>An <see cref="IMemberAccessor"/> for the field if found; otherwise, <c>null</c>.</returns>
487
    public IMemberAccessor? FindField(string name, BindingFlags flags)
488
    {
489
        // fast-path: avoid closure allocation from GetOrAdd lambda on cache hits
NEW
490
        if (_memberCache.TryGetValue(name, out var cached))
×
NEW
491
            return cached;
×
492

UNCOV
493
        return _memberCache.GetOrAdd(name, n => CreateFieldAccessor(n, flags));
×
494
    }
495

496
    private FieldAccessor? CreateFieldAccessor(string name, BindingFlags flags)
497
    {
498
        var info = FindField(Type, name, flags);
×
499
        return info == null ? null : CreateAccessor(info);
×
500
    }
501

502
    private static FieldInfo? FindField(Type type, string name, BindingFlags flags)
503
    {
504
        if (type == null)
×
505
            throw new ArgumentNullException(nameof(type));
×
506

507
        if (name == null)
×
508
            throw new ArgumentNullException(nameof(name));
×
509

510
        // first try GetField
511
        var typeInfo = type.GetTypeInfo();
×
512
        var field = typeInfo.GetField(name, flags);
×
513
        if (field != null)
×
514
            return field;
×
515

516
        // if not found, search while ignoring case
517
        return typeInfo
×
518
            .GetFields(flags)
×
519
            .FirstOrDefault(f => f.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
×
520
    }
521

522
    private static FieldAccessor? CreateAccessor(FieldInfo fieldInfo)
523
    {
524
        return fieldInfo == null ? null : new FieldAccessor(fieldInfo);
×
525
    }
526
    #endregion
527

528

529
    /// <summary>
530
    /// Gets the <see cref="TypeAccessor"/> for the specified Type.
531
    /// </summary>
532
    /// <typeparam name="T">The Type to get the accessor for.</typeparam>
533
    /// <returns></returns>
534
    public static TypeAccessor GetAccessor<T>()
535
    {
536
        return GetAccessor(typeof(T));
126✔
537
    }
538

539
    /// <summary>
540
    /// Gets the <see cref="TypeAccessor"/> for the specified <see cref="Type"/>.
541
    /// </summary>
542
    /// <param name="type">The type to get the accessor for.</param>
543
    /// <returns>The <see cref="TypeAccessor"/> for the specified type.</returns>
544
    public static TypeAccessor GetAccessor(Type type)
545
    {
546
        // fast-path: avoid overhead of GetOrAdd on cache hits
547
        if (_typeCache.TryGetValue(type, out var cached))
126✔
548
            return cached;
122✔
549

550
        return _typeCache.GetOrAdd(type, static t => new TypeAccessor(t));
8✔
551
    }
552

553

554
    /// <summary>
555
    /// Registers a pre-generated <see cref="TypeAccessor"/> for the specified <see cref="Type"/>,
556
    /// allowing source-generated metadata to be used instead of runtime reflection.
557
    /// </summary>
558
    /// <param name="type">The type to register the accessor for.</param>
559
    /// <param name="accessor">The pre-generated accessor.</param>
560
    public static void Register(Type type, TypeAccessor accessor)
561
    {
562
        if (type == null) throw new ArgumentNullException(nameof(type));
135!
563
        if (accessor == null) throw new ArgumentNullException(nameof(accessor));
135!
564
        _typeCache[type] = accessor;
135✔
565
    }
135✔
566

567

568
    /// <summary>
569
    /// Pre-registers a member accessor by name so that subsequent lookups
570
    /// via <see cref="Find(string)"/>, <see cref="FindProperty(string)"/>, or <see cref="FindColumn(string)"/>
571
    /// return it without reflection.
572
    /// </summary>
573
    /// <param name="name">The lookup key (property name or column name).</param>
574
    /// <param name="accessor">The member accessor to register.</param>
575
    protected void RegisterMember(string name, IMemberAccessor accessor)
576
    {
577
        _memberCache.TryAdd(name, accessor);
1,511✔
578
    }
1,511✔
579

580
    /// <summary>
581
    /// Pre-registers a collection of property accessors for the specified binding flags,
582
    /// so that <see cref="GetProperties(BindingFlags)"/> returns them without reflection.
583
    /// </summary>
584
    /// <param name="flags">The binding flags key.</param>
585
    /// <param name="properties">The property accessors to register.</param>
586
    protected void RegisterProperties(BindingFlags flags, IEnumerable<IMemberAccessor> properties)
587
    {
588
        _propertyCache.TryAdd((int)flags, [.. properties]);
135✔
589
    }
135✔
590
}
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