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

dennisdoomen / reflectify / 23978952007

04 Apr 2026 12:31PM UTC coverage: 95.801% (-1.5%) from 97.293%
23978952007

push

github

dennisdoomen
Improve the performance of getting fields and properties

Optimize property and field caching, replace `HasFlag` with bitwise operations, and improve `ToArray` and `Enumerator` implementations.

211 of 224 branches covered (94.2%)

Branch coverage included in aggregate %.

58 of 63 new or added lines in 1 file covered. (92.06%)

2 existing lines in 1 file now uncovered.

405 of 419 relevant lines covered (96.66%)

5250619.99 hits per line

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

95.8
/src/Reflectify/Reflectify.cs
1
#if !REFLECTIFY_COMPILE
2
// <autogenerated />
3
#pragma warning disable
4
#endif
5

6
#nullable disable
7

8
using System;
9
using System.Collections;
10
using System.Collections.Concurrent;
11
using System.Collections.Generic;
12
using System.Linq;
13
using System.Reflection;
14
using System.Runtime.CompilerServices;
15
using System.Text;
16

17
namespace Reflectify;
18

19
internal static class TypeMetaDataExtensions
20
{
21
    /// <summary>
22
    /// Returns the name of the type without the generic backtick and the type arguments.
23
    /// </summary>
24
    public static string GetNonGenericName(this Type type)
25
    {
8✔
26
        string name = type.Name;
8✔
27
        int index = name.IndexOf('`');
8✔
28
        return index == -1 ? name : name.Substring(0, index);
8✔
29
    }
8✔
30

31
    /// <summary>
32
    /// Returns <see langword="true" /> if the type is derived from an open-generic type, or <see langword="false" /> otherwise.
33
    /// </summary>
34
    public static bool IsDerivedFromOpenGeneric(this Type type, Type openGenericType)
35
    {
6✔
36
        // do not consider a type to be derived from itself
37
        if (type == openGenericType)
6✔
38
        {
2✔
39
            return false;
2✔
40
        }
41

42
        // check subject and its base types against definition
43
        for (Type baseType = type;
4✔
44
             baseType is not null;
10✔
45
             baseType = baseType.BaseType)
6✔
46
        {
8✔
47
            if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == openGenericType)
8✔
48
            {
2✔
49
                return true;
2✔
50
            }
51
        }
6✔
52

53
        return false;
2✔
54
    }
6✔
55

56
    /// <summary>
57
    /// Returns the interfaces that the <paramref name="type"/> implements or inherits from that are concrete
58
    /// versions of the <paramref name="openGenericType"/>.
59
    /// </summary>
60
    public static Type[] GetClosedGenericInterfaces(this Type type, Type openGenericType)
61
    {
8✔
62
        if (type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType)
8!
63
        {
×
64
            return [type];
×
65
        }
66

67
        Type[] interfaces = type.GetInterfaces();
8✔
68

69
        return interfaces
8✔
70
            .Where(t => t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == openGenericType))
10✔
71
            .ToArray();
8✔
72
    }
8✔
73

74
    /// <summary>
75
    /// Returns <see langword="true" /> if the type is decorated with the specific <typeparamref name="TAttribute"/>,
76
    /// or <see langword="false" /> otherwise.
77
    /// </summary>
78
    public static bool HasAttribute<TAttribute>(this Type type)
79
        where TAttribute : Attribute
80
    {
22✔
81
        return type.IsDefined(typeof(TAttribute), inherit: false);
22✔
82
    }
22✔
83

84
    /// <summary>
85
    /// Returns <see langword="true" /> if the type is decorated with the specific <typeparamref name="TAttribute"/> <i>and</i>
86
    /// that attribute instance matches the predicate, or <see langword="false" /> otherwise.
87
    /// </summary>
88
    public static bool HasAttribute<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
89
        where TAttribute : Attribute
90
    {
10✔
91
        if (predicate is null)
10✔
92
        {
2✔
93
            throw new ArgumentNullException(nameof(predicate));
2✔
94
        }
95

96
        return type.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
8✔
97
    }
8✔
98

99
    /// <summary>
100
    /// Returns <see langword="true" /> if the type or one its parents are decorated with the
101
    /// specific <typeparamref name="TAttribute"/>.
102
    /// </summary>
103
    public static bool HasAttributeInHierarchy<TAttribute>(this Type type)
104
        where TAttribute : Attribute
105
    {
6✔
106
        return type.IsDefined(typeof(TAttribute), inherit: true);
6✔
107
    }
6✔
108

109
    /// <summary>
110
    /// Returns <see langword="true" /> if the type or one its parents are decorated with the
111
    /// specific <typeparamref name="TAttribute"/> <i>and</i> that attribute instance
112
    /// matches the predicate. Returns <see langword="false" /> otherwise.
113
    /// </summary>
114
    public static bool HasAttributeInHierarchy<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
115
        where TAttribute : Attribute
116
    {
8✔
117
        if (predicate is null)
8✔
118
        {
4✔
119
            throw new ArgumentNullException(nameof(predicate));
4✔
120
        }
121

122
        return type.GetCustomAttributes<TAttribute>(inherit: true).Any(predicate);
4✔
123
    }
4✔
124

125
    /// <summary>
126
    /// Retrieves all custom attributes of the specified type from a class or its inheritance hierarchy.
127
    /// </summary>
128
    public static TAttribute[] GetMatchingAttributes<TAttribute>(this Type type)
129
        where TAttribute : Attribute
130
    {
4✔
131
        return (TAttribute[])type.GetCustomAttributes<TAttribute>(inherit: true);
4✔
132
    }
4✔
133

134
    /// <summary>
135
    /// Retrieves an array of attributes of a specified type that match the provided predicate.
136
    /// </summary>
137
    public static TAttribute[] GetMatchingAttributes<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
138
        where TAttribute : Attribute
139
    {
8✔
140
        if (predicate is null)
8!
141
        {
×
142
            throw new ArgumentNullException(nameof(predicate));
×
143
        }
144

145
        return type.GetCustomAttributes<TAttribute>(inherit: true).Where(predicate).ToArray();
8✔
146
    }
8✔
147

148
    /// <summary>
149
    /// Returns <see langword="true" /> if the type overrides the Equals method, or <see langword="false" /> otherwise.
150
    /// </summary>
151
    public static bool OverridesEquals(this Type type)
152
    {
4✔
153
        MethodInfo method = type
4✔
154
            .GetMethod("Equals", [typeof(object)]);
4✔
155

156
        return method is not null
4!
157
               && method.GetBaseDefinition().DeclaringType != method.DeclaringType;
4✔
158
    }
4✔
159

160
    /// <summary>
161
    /// Determines whether the actual type is the same as, or inherits from, the expected type.
162
    /// </summary>
163
    /// <remarks>
164
    /// The expected type can also be an open generic type definition.
165
    /// </remarks>
166
    /// <returns><see langword="true" /> if the actual type is the same as, or inherits from, the expected type; otherwise, <see langword="false" />.</returns>
167
    public static bool IsSameOrInherits<TExpectedType>(this Type actualType)
168
    {
12✔
169
        return actualType.IsSameOrInherits(typeof(TExpectedType));
12✔
170
    }
12✔
171

172
    /// <summary>
173
    /// Determines whether the actual type is the same as, or inherits from, the expected type.
174
    /// </summary>
175
    /// <remarks>
176
    /// The expected type can also be an open generic type definition.
177
    /// </remarks>
178
    /// <returns><see langword="true" /> if the actual type is the same as, or inherits from, the expected type; otherwise, <see langword="false" />.</returns>
179
    public static bool IsSameOrInherits(this Type actualType, Type expectedType)
180
    {
24✔
181
        return actualType == expectedType ||
24✔
182
               expectedType.IsAssignableFrom(actualType) ||
24✔
183
               (actualType.BaseType is { IsGenericType: true } && actualType.BaseType.GetGenericTypeDefinition() == expectedType);
24✔
184
    }
24✔
185

186
    /// <summary>
187
    /// Returns <see langword="true" /> if the type is a delegate type; otherwise, <see langword="false" />.
188
    /// </summary>
189
    public static bool IsDelegate(this Type type)
190
    {
6✔
191
        return typeof(Delegate).IsAssignableFrom(type);
6✔
192
    }
6✔
193

194
    /// <summary>
195
    /// Returns <see langword="true" /> if the type is a compiler-generated type, or <see langword="false" /> otherwise.
196
    /// </summary>
197
    /// <remarks>
198
    /// Typical examples of compiler-generated types are anonymous types, tuples, and records.
199
    /// </remarks>
200
    public static bool IsCompilerGenerated(this Type type)
201
    {
10✔
202
        return type.HasAttribute<CompilerGeneratedAttribute>() ||
10✔
203
               type.IsRecord() ||
10✔
204
               type.IsTuple();
10✔
205
    }
10✔
206

207
    /// <summary>
208
    /// Returns <see langword="true" /> if the type has a readable name, or <see langword="false" />
209
    /// if it is a compiler-generated name.
210
    /// </summary>
211
    public static bool HasFriendlyName(this Type type)
212
    {
6✔
213
        return !type.IsAnonymous() && !type.IsTuple();
6✔
214
    }
6✔
215

216
    /// <summary>
217
    /// Return <see langword="true" /> if the type is a tuple type; otherwise, <see langword="false" />
218
    /// </summary>
219
    public static bool IsTuple(this Type type)
220
    {
14✔
221
        if (!type.IsGenericType)
14✔
222
        {
6✔
223
            return false;
6✔
224
        }
225

226
#if NETCOREAPP2_0_OR_GREATER || NET471_OR_GREATER || NETSTANDARD2_1_OR_GREATER
227
        return typeof(ITuple).IsAssignableFrom(type);
8✔
228
#else
229
        Type openType = type.GetGenericTypeDefinition();
230

231
        return openType == typeof(ValueTuple<>)
232
               || openType == typeof(ValueTuple<,>)
233
               || openType == typeof(ValueTuple<,,>)
234
               || openType == typeof(ValueTuple<,,,>)
235
               || openType == typeof(ValueTuple<,,,,>)
236
               || openType == typeof(ValueTuple<,,,,,>)
237
               || openType == typeof(ValueTuple<,,,,,,>)
238
               || (openType == typeof(ValueTuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]))
239
               || openType == typeof(Tuple<>)
240
               || openType == typeof(Tuple<,>)
241
               || openType == typeof(Tuple<,,>)
242
               || openType == typeof(Tuple<,,,>)
243
               || openType == typeof(Tuple<,,,,>)
244
               || openType == typeof(Tuple<,,,,,>)
245
               || openType == typeof(Tuple<,,,,,,>)
246
               || (openType == typeof(Tuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]));
247
#endif
248
    }
14✔
249

250
    /// <summary>
251
    /// Returns <see langword="true" /> if the type is an anonymous type, or <see langword="false" /> otherwise.
252
    /// </summary>
253
    public static bool IsAnonymous(this Type type)
254
    {
12✔
255
        if (!type.FullName!.Contains("AnonymousType"))
12✔
256
        {
8✔
257
            return false;
8✔
258
        }
259

260
        return type.HasAttribute<CompilerGeneratedAttribute>();
4✔
261
    }
12✔
262

263
    /// <summary>
264
    /// Return <see langword="true" /> if the type is a struct or class record type; otherwise, <see langword="false" />.
265
    /// </summary>
266
    public static bool IsRecord(this Type type)
267
    {
12✔
268
        return type.IsRecordClass() || type.IsRecordStruct();
12✔
269
    }
12✔
270

271
    /// <summary>
272
    /// Returns <see langword="true" /> if the type is a class record type; otherwise, <see langword="false" />.
273
    /// </summary>
274
    public static bool IsRecordClass(this Type type)
275
    {
18✔
276
        return type.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) is { } &&
18!
277
               type.GetProperty("EqualityContract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)?
18✔
278
                   .GetMethod?.HasAttribute<CompilerGeneratedAttribute>() == true;
18✔
279
    }
18✔
280

281
    /// <summary>
282
    /// Return <see langword="true" /> if the type is a record struct; otherwise, <see langword="false" />
283
    /// </summary>
284
    public static bool IsRecordStruct(this Type type)
285
    {
16✔
286
        // As noted here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs#open-questions
287
        // recognizing record structs from metadata is an open point. The following check is based on common sense
288
        // and heuristic testing, apparently giving good results but not supported by official documentation.
289
        return type.BaseType == typeof(ValueType) &&
16!
290
               type.GetMethod("PrintMembers", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, null,
16✔
291
                   [typeof(StringBuilder)], null) is { } &&
16✔
292
               type.GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly, null,
16✔
293
                       [type, type], null)?
16✔
294
                   .HasAttribute<CompilerGeneratedAttribute>() == true;
16✔
295
    }
16✔
296

297
    /// <summary>
298
    /// Determines whether the specified type is a KeyValuePair.
299
    /// </summary>
300
    /// <param name="type">The type to check.</param>
301
    /// <returns><see langword="true" /> if the type is a KeyValuePair; otherwise, <see langword="false" />.</returns>
302
    public static bool IsKeyValuePair(this Type type)
303
    {
14✔
304
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
14✔
305
    }
14✔
306

307
    /// <summary>
308
    /// Returns <see langword="true" /> if the type is a struct (i.e., a value type that is not an enum);
309
    /// otherwise, <see langword="false" />.
310
    /// </summary>
311
    public static bool IsStruct(this Type type)
312
    {
8✔
313
        return type.IsValueType && !type.IsEnum;
8✔
314
    }
8✔
315

316
    /// <summary>
317
    /// Returns <see langword="true" /> if the type is a ref struct; otherwise, <see langword="false" />.
318
    /// </summary>
319
    /// <remarks>
320
    /// Returns <see langword="false" /> on .NET versions that do not support ref structs.
321
    /// </remarks>
322
    public static bool IsRefStruct(this Type type)
323
    {
8✔
324
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
325
        return type.IsDefined(typeof(IsByRefLikeAttribute), false);
8✔
326
#else
327
        return false;
328
#endif
329
    }
8✔
330
}
331

332
internal static class TypeMemberExtensions
333
{
334
    private static readonly ConcurrentDictionary<(Type Type, MemberKind Kind), Reflector> ReflectorCache = new();
2✔
335

336
    /// <summary>
337
    /// Gets the public, internal, explicitly implemented and/or default properties of a type hierarchy.
338
    /// </summary>
339
    /// <param name="type">The type to reflect.</param>
340
    /// <param name="kind">The kind of properties to include in the response.</param>
341
    public static PropertyInfo[] GetProperties(this Type type, MemberKind kind)
342
    {
200,000,052✔
343
        return GetFor(type, kind).Properties;
200,000,052✔
344
    }
200,000,052✔
345

346
    /// <summary>
347
    /// Finds the property by a case-sensitive name and with a certain visibility.
348
    /// </summary>
349
    /// <remarks>
350
    /// Normal property get priority over explicitly implemented properties and default interface properties.
351
    /// </remarks>
352
    /// <returns>
353
    /// Returns <see langword="null" /> if no such property exists.
354
    /// </returns>
355
    public static PropertyInfo FindProperty(this Type type, string propertyName, MemberKind memberVisibility)
356
    {
32✔
357
        if (propertyName is null or "")
32✔
358
        {
4✔
359
            throw new ArgumentException("The property name cannot be null or empty", nameof(propertyName));
4✔
360
        }
361

362
        var properties = type.GetProperties(memberVisibility);
28✔
363

364
        return Array.Find(properties, p =>
28✔
365
            p.Name == propertyName || p.Name.EndsWith("." + propertyName, StringComparison.Ordinal));
80✔
366
    }
28✔
367

368
    /// <summary>
369
    /// Gets the public and/or internal fields of a type hierarchy.
370
    /// </summary>
371
    /// <param name="type">The type to reflect.</param>
372
    /// <param name="kind">The kind of fields to include in the response.</param>
373
    public static FieldInfo[] GetFields(this Type type, MemberKind kind)
374
    {
24✔
375
        return GetFor(type, kind).Fields;
24✔
376
    }
24✔
377

378
    /// <summary>
379
    /// Finds the field by a case-sensitive name.
380
    /// </summary>
381
    /// <returns>
382
    /// Returns <see langword="null" /> if no such field exists.
383
    /// </returns>
384
    public static FieldInfo FindField(this Type type, string fieldName, MemberKind memberVisibility)
385
    {
18✔
386
        if (fieldName is null or "")
18✔
387
        {
4✔
388
            throw new ArgumentException("The field name cannot be null or empty", nameof(fieldName));
4✔
389
        }
390

391
        var fields = type.GetFields(memberVisibility);
14✔
392

393
        return Array.Find(fields, p => p.Name == fieldName);
28✔
394
    }
14✔
395

396
    /// <summary>
397
    /// Gets a combination of <see cref="GetProperties"/> and <see cref="GetFields"/>.
398
    /// </summary>
399
    /// <param name="type">The type to reflect.</param>
400
    /// <param name="kind">The kind of fields and properties to include in the response.</param>
401
    public static MemberInfo[] GetMembers(this Type type, MemberKind kind)
402
    {
4✔
403
        return GetFor(type, kind).Members;
4✔
404
    }
4✔
405

406
    private static Reflector GetFor(Type typeToReflect, MemberKind kind)
407
    {
200,000,080✔
408
        return ReflectorCache.GetOrAdd((typeToReflect, kind),
200,000,080✔
409
            static key => new Reflector(key.Type, key.Kind));
200,000,110✔
410
    }
200,000,080✔
411

412
    /// <summary>
413
    /// Finds a method by its name, parameter types and visibility. Returns <see langword="null" /> if no such method exists.
414
    /// </summary>
415
    /// <remarks>
416
    /// If you don't specify any parameter types, the method will be found by its name only.
417
    /// </remarks>
418
#pragma warning disable AV1561
419
    public static MethodInfo FindMethod(this Type type, string methodName, MemberKind kind, params Type[] parameterTypes)
420
#pragma warning restore AV1561
421
    {
30✔
422
        if (methodName is null or "")
30✔
423
        {
4✔
424
            throw new ArgumentException("The method name cannot be null or empty", nameof(methodName));
4✔
425
        }
426

427
        var flags = kind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
26✔
428

429
        return type
26✔
430
            .GetMethods(flags)
26✔
431
            .SingleOrDefault(m => m.Name == methodName && HasSameParameters(parameterTypes, m));
196✔
432
    }
26✔
433

434
    /// <summary>
435
    /// Finds a parameterless method by its name and visibility. Returns <see langword="null" /> if no such method exists.
436
    /// </summary>
437
    public static MethodInfo FindParameterlessMethod(this Type type, string methodName, MemberKind memberKind)
438
    {
2✔
439
        return type.FindMethod(methodName, memberKind);
2✔
440
    }
2✔
441

442
    private static bool HasSameParameters(Type[] parameterTypes, MethodInfo method)
443
    {
20✔
444
        if (parameterTypes.Length == 0)
20✔
445
        {
12✔
446
            // If we don't specify any specific parameters, it matches always.
447
            return true;
12✔
448
        }
449

450
        return method.GetParameters()
8✔
451
            .Select(p => p.ParameterType)
22✔
452
            .SequenceEqual(parameterTypes);
8✔
453
    }
20✔
454

455
    /// <summary>
456
    /// Determines whether the type has a method with the specified name and visibility.
457
    /// </summary>
458
#pragma warning disable AV1561
459
    public static bool HasMethod(this Type type, string methodName, MemberKind memberKind, params Type[] parameterTypes)
460
#pragma warning restore AV1561
461
    {
4✔
462
        return type.FindMethod(methodName, memberKind, parameterTypes) is not null;
4✔
463
    }
4✔
464

465
    public static PropertyInfo FindIndexer(this Type type, MemberKind memberKind, params Type[] parameterTypes)
466
    {
10✔
467
        var flags = memberKind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
10✔
468

469
        return type.GetProperties(flags)
10✔
470
            .SingleOrDefault(p =>
10✔
471
                p.IsIndexer() && p.GetIndexParameters().Select(i => i.ParameterType).SequenceEqual(parameterTypes));
36✔
472
    }
10✔
473

474
#pragma warning disable AV1561
475

476
    /// <summary>
477
    /// Finds an explicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
478
    /// Returns <see langword="null" /> if no such operator exists.
479
    /// </summary>
480
    public static MethodInfo FindExplicitConversionOperator(this Type type, Type sourceType, Type targetType)
481
    {
4✔
482
        return type
4✔
483
            .GetConversionOperators(sourceType, targetType, name => name is "op_Explicit")
2✔
484
            .SingleOrDefault();
4✔
485
    }
4✔
486

487
    /// <summary>
488
    /// Finds an implicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
489
    /// Returns <see langword="null" /> if no such operator exists.
490
    /// </summary>
491
    public static MethodInfo FindImplicitConversionOperator(this Type type, Type sourceType, Type targetType)
492
    {
4✔
493
        return type
4✔
494
            .GetConversionOperators(sourceType, targetType, name => name is "op_Implicit")
2✔
495
            .SingleOrDefault();
4✔
496
    }
4✔
497

498
    private static IEnumerable<MethodInfo> GetConversionOperators(this Type type, Type sourceType, Type targetType,
499
#pragma warning restore AV1561
500
        Func<string, bool> predicate)
501
    {
8✔
502
        return type
8✔
503
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
8✔
504
            .Where(m =>
8✔
505
                m.IsPublic
24✔
506
                && m.IsStatic
24✔
507
                && m.IsSpecialName
24✔
508
                && m.ReturnType == targetType
24✔
509
                && predicate(m.Name)
24✔
510
                && m.GetParameters() is { Length: 1 } parameters
24✔
511
                && parameters[0].ParameterType == sourceType);
24✔
512
    }
8✔
513
}
514

515
internal static class TypeExtensions
516
{
517
    /// <summary>
518
    /// If the type provided is a nullable type, gets the underlying type. Returns the type itself otherwise.
519
    /// </summary>
520
    public static Type NullableOrActualType(this Type type)
521
    {
6✔
522
        if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
6✔
523
        {
2✔
524
            type = type.GetGenericArguments()[0];
2✔
525
        }
2✔
526

527
        return type;
6✔
528
    }
6✔
529
}
530

531
internal static class MemberInfoExtensions
532
{
533
    public static bool HasAttribute<TAttribute>(this MemberInfo member)
534
        where TAttribute : Attribute
535
    {
10✔
536
        // Do not use MemberInfo.IsDefined
537
        // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work.
538
        // https://github.com/dotnet/runtime/issues/30219
539
        return Attribute.IsDefined(member, typeof(TAttribute), inherit: false);
10✔
540
    }
10✔
541

542
    /// <summary>
543
    /// Determines whether the member has an attribute of the specified type that matches the predicate.
544
    /// </summary>
545
    public static bool HasAttribute<TAttribute>(this MemberInfo member, Func<TAttribute, bool> predicate)
546
        where TAttribute : Attribute
547
    {
6✔
548
        if (predicate is null)
6✔
549
        {
2✔
550
            throw new ArgumentNullException(nameof(predicate));
2✔
551
        }
552

553
        return member.GetCustomAttributes<TAttribute>().Any(a => predicate(a));
8✔
554
    }
4✔
555

556
    public static bool HasAttributeInHierarchy<TAttribute>(this MemberInfo member)
557
        where TAttribute : Attribute
558
    {
4✔
559
        // Do not use MemberInfo.IsDefined
560
        // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work.
561
        // https://github.com/dotnet/runtime/issues/30219
562
        return Attribute.IsDefined(member, typeof(TAttribute), inherit: true);
4✔
563
    }
4✔
564
}
565

566
internal static class ParameterInfoExtensions
567
{
568
    /// <summary>
569
    /// Returns <see langword="true" /> if the parameter is decorated with the specific <typeparamref name="TAttribute"/>,
570
    /// or <see langword="false" /> otherwise.
571
    /// </summary>
572
    public static bool HasAttribute<TAttribute>(this ParameterInfo parameter)
573
        where TAttribute : Attribute
574
    {
4✔
575
        return parameter.IsDefined(typeof(TAttribute), inherit: false);
4✔
576
    }
4✔
577

578
    /// <summary>
579
    /// Returns <see langword="true" /> if the parameter is decorated with the specific <typeparamref name="TAttribute"/> <i>and</i>
580
    /// that attribute instance matches the predicate, or <see langword="false" /> otherwise.
581
    /// </summary>
582
    public static bool HasAttribute<TAttribute>(this ParameterInfo parameter, Func<TAttribute, bool> predicate)
583
        where TAttribute : Attribute
584
    {
6✔
585
        if (predicate is null)
6✔
586
        {
2✔
587
            throw new ArgumentNullException(nameof(predicate));
2✔
588
        }
589

590
        return parameter.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
4✔
591
    }
4✔
592

593
    /// <summary>
594
    /// Returns <see langword="true" /> if the parameter or one of its overridden definitions are decorated with the
595
    /// specific <typeparamref name="TAttribute"/>.
596
    /// </summary>
597
    public static bool HasAttributeInHierarchy<TAttribute>(this ParameterInfo parameter)
598
        where TAttribute : Attribute
599
    {
4✔
600
        return parameter.IsDefined(typeof(TAttribute), inherit: true);
4✔
601
    }
4✔
602
}
603

604
internal static class PropertyInfoExtensions
605
{
606
    /// <summary>
607
    /// Returns <see langword="true" /> if the property is an indexer, or <see langword="false" /> otherwise.
608
    /// </summary>
609
    public static bool IsIndexer(this PropertyInfo member)
610
    {
82✔
611
        return member.GetIndexParameters().Length != 0;
82✔
612
    }
82✔
613

614
    /// <summary>
615
    /// Returns <see langword="true" /> if the property is explicitly implemented on the
616
    /// <see cref="MemberInfo.DeclaringType"/>, or <see langword="false" /> otherwise.
617
    /// </summary>
618
    public static bool IsExplicitlyImplemented(this PropertyInfo prop)
619
    {
92✔
620
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
621
        return prop.Name.Contains('.');
92✔
622
#else
623
        return prop.Name.IndexOf('.') != -1;
624
#endif
625
    }
92✔
626

627
    /// <summary>
628
    /// Returns <see langword="true" /> if the property has a public getter or setter, or <see langword="false" /> otherwise.
629
    /// </summary>
630
    public static bool IsPublic(this PropertyInfo prop)
631
    {
70✔
632
        return prop.GetMethod is { IsPublic: true } || prop.SetMethod is { IsPublic: true };
70✔
633
    }
70✔
634

635
    /// <summary>
636
    /// Returns <see langword="true" /> if the property is internal, or <see langword="false" /> otherwise.
637
    /// </summary>
638
    /// <param name="prop">The property to check.</param>
639
    public static bool IsInternal(this PropertyInfo prop)
640
    {
20✔
641
        return prop.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true } ||
20!
642
               prop.SetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
20✔
643
    }
20✔
644

645
    /// <summary>
646
    /// Returns <see langword="true" /> if the property is abstract, or <see langword="false" /> otherwise.
647
    /// </summary>
648
    /// <param name="prop">The property to check.</param>
649
    public static bool IsAbstract(this PropertyInfo prop)
650
    {
22✔
651
        return prop.GetMethod is { IsAbstract: true } || prop.SetMethod is { IsAbstract: true };
22✔
652
    }
22✔
653
}
654

655
/// <summary>
656
/// Defines the kinds of members you want to get when querying for the fields and properties of a type.
657
/// </summary>
658
[Flags]
659
internal enum MemberKind
660
{
661
    None,
662
    Public = 1,
663
    Internal = 2,
664
    ExplicitlyImplemented = 4,
665
    DefaultInterfaceProperties = 8,
666
    Static = 16
667
}
668

669
internal static class MemberKindExtensions
670
{
671
    public static BindingFlags ToBindingFlags(this MemberKind kind)
672
    {
36✔
673
        BindingFlags flags = BindingFlags.Default;
36✔
674

675
        if ((kind & MemberKind.Public) != MemberKind.None)
36✔
676
        {
30✔
677
            flags |= BindingFlags.Public;
30✔
678
        }
30✔
679

680
        if ((kind & MemberKind.Internal) != MemberKind.None)
36✔
681
        {
6✔
682
            flags |= BindingFlags.NonPublic;
6✔
683
        }
6✔
684

685
        return flags;
36✔
686
    }
36✔
687
}
688

689
/// <summary>
690
/// Helper class to get all the public and internal fields and properties from a type.
691
/// </summary>
692
internal sealed class Reflector(Type typeToReflect, MemberKind kind)
30✔
693
{
694
    private readonly List<FieldInfo> selectedFields = new();
30✔
695
    private readonly OrderedPropertyCollection selectedProperties = new();
30✔
696
    private readonly object lazyLoadingLock = new();
30✔
697
    private volatile PropertyInfo[] cachedProperties;
698
    private volatile FieldInfo[] cachedFields;
699

700
    public MemberInfo[] Members => [.. Properties, .. Fields];
4✔
701

702
    public PropertyInfo[] Properties
703
    {
704
        get
705
        {
200,000,056✔
706
            if (cachedProperties is null)
200,000,056✔
707
            {
28✔
708
                lock (lazyLoadingLock)
28✔
709
                {
28✔
710
                    if (cachedProperties is null)
28✔
711
                    {
28✔
712
                        LoadProperties(typeToReflect, kind);
28✔
713
                        cachedProperties = selectedProperties.ToArray();
28✔
714
                    }
28✔
715
                }
28✔
716
            }
28✔
717

718
            return cachedProperties;
200,000,056✔
719
        }
200,000,056✔
720
    }
721

722
    public FieldInfo[] Fields
723
    {
724
        get
725
        {
28✔
726
            if (cachedFields is null)
28✔
727
            {
12✔
728
                lock (lazyLoadingLock)
12✔
729
                {
12✔
730
                    if (cachedFields is null)
12✔
731
                    {
12✔
732
                        LoadFields(typeToReflect, kind);
12✔
733
                        cachedFields = selectedFields.ToArray();
12✔
734
                    }
12✔
735
                }
12✔
736
            }
12✔
737

738
            return cachedFields;
28✔
739
        }
28✔
740
    }
741

742
    private void LoadProperties(Type typeToReflect, MemberKind kind)
743
    {
28✔
744
        while (typeToReflect != null && typeToReflect != typeof(object))
70✔
745
        {
42✔
746
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
42✔
747
            flags |= (kind & MemberKind.Static) != MemberKind.None ? BindingFlags.Static : BindingFlags.Instance;
42✔
748

749
            var allProperties = typeToReflect.GetProperties(flags);
42✔
750

751
            AddNormalProperties(kind, allProperties);
42✔
752

753
            AddExplicitlyImplementedProperties(kind, allProperties);
42✔
754

755
            AddInterfaceProperties(typeToReflect, kind, flags);
42✔
756

757
            // Move to the base type
758
            typeToReflect = typeToReflect.BaseType;
42✔
759
        }
42✔
760
    }
28✔
761

762
    private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
763
    {
42✔
764
        if ((kind & (MemberKind.Public | MemberKind.Internal | MemberKind.ExplicitlyImplemented)) != MemberKind.None)
42✔
765
        {
36✔
766
            foreach (var property in allProperties)
292✔
767
            {
92✔
768
                if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
92✔
769
                {
50✔
770
                    selectedProperties.AddNormal(property);
50✔
771
                }
50✔
772
            }
92✔
773
        }
36✔
774
    }
42✔
775

776
    private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
777
    {
92✔
778
        return ((kind & MemberKind.Public) != MemberKind.None && prop.IsPublic()) ||
92✔
779
               ((kind & MemberKind.Internal) != MemberKind.None && prop.IsInternal());
92✔
780
    }
92✔
781

782
    private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
783
    {
42✔
784
        if ((kind & MemberKind.ExplicitlyImplemented) != MemberKind.None)
42✔
785
        {
16✔
786
            foreach (var property in allProperties)
124✔
787
            {
38✔
788
                if (property.IsExplicitlyImplemented())
38✔
789
                {
8✔
790
                    selectedProperties.AddExplicitlyImplemented(property);
8✔
791
                }
8✔
792
            }
38✔
793
        }
16✔
794
    }
42✔
795

796
#pragma warning disable AV1561
797
    private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
798
    {
42✔
799
        if ((kind & MemberKind.DefaultInterfaceProperties) != MemberKind.None || typeToReflect.IsInterface)
42✔
800
        {
12✔
801
            var interfaces = typeToReflect.GetInterfaces();
12✔
802

803
            foreach (var interfaceType in interfaces)
60✔
804
            {
12✔
805
                foreach (var prop in interfaceType.GetProperties(flags))
68✔
806
                {
16✔
807
                    if (!prop.IsAbstract() || typeToReflect.IsInterface)
16✔
808
                    {
6✔
809
                        selectedProperties.AddFromInterface(prop);
6✔
810
                    }
6✔
811
                }
16✔
812
            }
12✔
813
        }
12✔
814
    }
42✔
815
#pragma warning restore AV1561
816

817
    private void LoadFields(Type typeToReflect, MemberKind kind)
818
    {
12✔
819
        var collectedFieldNames = new HashSet<string>();
12✔
820

821
        while (typeToReflect != null && typeToReflect != typeof(object))
32!
822
        {
20✔
823
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
20✔
824
            flags |= (kind & MemberKind.Static) != MemberKind.None ? BindingFlags.Static : BindingFlags.Instance;
20✔
825

826
            var files = typeToReflect.GetFields(flags);
20✔
827

828
            foreach (var field in files)
196✔
829
            {
68✔
830
                if (HasVisibility(kind, field) && collectedFieldNames.Add(field.Name))
68✔
831
                {
14✔
832
                    selectedFields.Add(field);
14✔
833
                }
14✔
834
            }
68✔
835

836
            // Move to the base type
837
            typeToReflect = typeToReflect.BaseType;
20✔
838
        }
20✔
839
    }
12✔
840

841
    private static bool HasVisibility(MemberKind kind, FieldInfo field)
842
    {
68✔
843
        return ((kind & MemberKind.Public) != MemberKind.None && field.IsPublic) ||
68✔
844
               ((kind & MemberKind.Internal) != MemberKind.None && (field.IsAssembly || field.IsFamilyOrAssembly));
68✔
845
    }
68✔
846

847
    private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
848
    {
849
        private readonly Dictionary<string, PropertyKind> kindMap = new();
30✔
850
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
30✔
851

852
        public PropertyInfo[] ToArray()
853
        {
28✔
854
            var result = new PropertyInfo[propertiesWithName.Count];
28✔
855

856
            for (int i = 0; i < propertiesWithName.Count; i++)
164✔
857
            {
54✔
858
                result[i] = propertiesWithName[i].Property;
54✔
859
            }
54✔
860

861
            return result;
28✔
862
        }
28✔
863

864
        public IEnumerator<PropertyInfo> GetEnumerator()
UNCOV
865
        {
×
NEW
866
            foreach (var (_, property) in propertiesWithName)
×
NEW
867
            {
×
NEW
868
                yield return property;
×
NEW
869
            }
×
UNCOV
870
        }
×
871

872
        IEnumerator IEnumerable.GetEnumerator()
873
        {
×
874
            return GetEnumerator();
×
875
        }
×
876

877
        private enum PropertyKind
878
        {
879
            Normal,
880
            ExplicitlyImplemented,
881
            Interface
882
        }
883

884
        public void AddExplicitlyImplemented(PropertyInfo property)
885
        {
8✔
886
            var name = property.Name.Split('.').Last();
8✔
887

888
            Add(name, property, PropertyKind.ExplicitlyImplemented);
8✔
889
        }
8✔
890

891
        public void AddNormal(PropertyInfo property)
892
        {
50✔
893
            Add(property.Name, property, PropertyKind.Normal);
50✔
894
        }
50✔
895

896
        public void AddFromInterface(PropertyInfo property)
897
        {
6✔
898
            Add(property.Name, property, PropertyKind.Interface);
6✔
899
        }
6✔
900

901
        private void Add(string name, PropertyInfo property, PropertyKind kind)
902
        {
64✔
903
            if (property.IsIndexer())
64✔
904
            {
2✔
905
                // We explicitly skip indexers
906
            }
2✔
907
            else if (!kindMap.TryGetValue(name, out var existingKind))
62✔
908
            {
54✔
909
                kindMap[name] = kind;
54✔
910
                propertiesWithName.Add((name, property));
54✔
911
            }
54✔
912
            else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
8✔
913
            {
2✔
914
                // Normal properties have priority over explicitly implemented properties
915
                kindMap[name] = kind;
2✔
916

917
                for (int i = 0; i < propertiesWithName.Count; i++)
4✔
918
                {
2✔
919
                    if (propertiesWithName[i].Name == name)
2!
920
                    {
2✔
921
                        propertiesWithName.RemoveAt(i);
2✔
922
                        break;
2✔
923
                    }
NEW
924
                }
×
925

926
                propertiesWithName.Add((name, property));
2✔
927
            }
2✔
928
            else
929
            {
6✔
930
                // Property with that name already exists
931
            }
6✔
932
        }
64✔
933
    }
934
}
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