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

dennisdoomen / reflectify / 24037189019

06 Apr 2026 03:06PM UTC coverage: 96.994% (+1.2%) from 95.801%
24037189019

Pull #143

github

web-flow
Merge 6173ab708 into 557ef6783
Pull Request #143: Performance improvements

210 of 222 branches covered (94.59%)

Branch coverage included in aggregate %.

11 of 11 new or added lines in 1 file covered. (100.0%)

2 existing lines in 1 file now uncovered.

403 of 410 relevant lines covered (98.29%)

5365877.43 hits per line

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

96.99
/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.Concurrent;
10
using System.Collections.Generic;
11
using System.Linq;
12
using System.Reflection;
13
using System.Runtime.CompilerServices;
14
using System.Text;
15

16
namespace Reflectify;
17

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

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

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

52
        return false;
2✔
53
    }
6✔
54

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

230
        return openType == typeof(ValueTuple<>)
231
               || 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<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]))
238
               || openType == typeof(Tuple<>)
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<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]));
246
#endif
247
    }
14✔
248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

473
#pragma warning disable AV1561
474

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

697
    public MemberInfo[] Members => [.. Properties, .. Fields];
4✔
698

699
    public PropertyInfo[] Properties
700
    {
701
        get
702
        {
200,000,056✔
703
            if (cachedProperties is null)
200,000,056✔
704
            {
28✔
705
                lock (lazyLoadingLock)
28✔
706
                {
28✔
707
                    if (cachedProperties is null)
28✔
708
                    {
28✔
709
                        cachedProperties = LoadProperties(typeToReflect, kind);
28✔
710
                    }
28✔
711
                }
28✔
712
            }
28✔
713

714
            return cachedProperties;
200,000,056✔
715
        }
200,000,056✔
716
    }
717

718
    public FieldInfo[] Fields
719
    {
720
        get
721
        {
28✔
722
            if (cachedFields is null)
28✔
723
            {
12✔
724
                lock (lazyLoadingLock)
12✔
725
                {
12✔
726
                    if (cachedFields is null)
12✔
727
                    {
12✔
728
                        cachedFields = LoadFields(typeToReflect, kind);
12✔
729
                    }
12✔
730
                }
12✔
731
            }
12✔
732

733
            return cachedFields;
28✔
734
        }
28✔
735
    }
736

737
    private static PropertyInfo[] LoadProperties(Type typeToReflect, MemberKind kind)
738
    {
28✔
739
        var selectedProperties = new OrderedPropertyCollection();
28✔
740

741
        while (typeToReflect != null && typeToReflect != typeof(object))
70✔
742
        {
42✔
743
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
42✔
744
            flags |= (kind & MemberKind.Static) != MemberKind.None ? BindingFlags.Static : BindingFlags.Instance;
42✔
745

746
            var allProperties = typeToReflect.GetProperties(flags);
42✔
747

748
            AddNormalProperties(kind, allProperties, selectedProperties);
42✔
749

750
            AddExplicitlyImplementedProperties(kind, allProperties, selectedProperties);
42✔
751

752
            AddInterfaceProperties(typeToReflect, kind, flags, selectedProperties);
42✔
753

754
            // Move to the base type
755
            typeToReflect = typeToReflect.BaseType;
42✔
756
        }
42✔
757

758
        return selectedProperties.ToArray();
28✔
759
    }
28✔
760

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

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

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

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

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

816
    private static FieldInfo[] LoadFields(Type typeToReflect, MemberKind kind)
817
    {
12✔
818
        var selectedFields = new List<FieldInfo>();
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

840
        return selectedFields.ToArray();
12✔
841
    }
12✔
842

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

849
    private class OrderedPropertyCollection
850
    {
851
        private readonly Dictionary<string, PropertyKind> kindMap = new();
28✔
852
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
28✔
853

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

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

863
            return result;
28✔
864
        }
28✔
865

866
        private enum PropertyKind
867
        {
868
            Normal,
869
            ExplicitlyImplemented,
870
            Interface
871
        }
872

873
        public void AddExplicitlyImplemented(PropertyInfo property)
874
        {
8✔
875
            var name = property.Name.Split('.').Last();
8✔
876

877
            Add(name, property, PropertyKind.ExplicitlyImplemented);
8✔
878
        }
8✔
879

880
        public void AddNormal(PropertyInfo property)
881
        {
50✔
882
            Add(property.Name, property, PropertyKind.Normal);
50✔
883
        }
50✔
884

885
        public void AddFromInterface(PropertyInfo property)
886
        {
6✔
887
            Add(property.Name, property, PropertyKind.Interface);
6✔
888
        }
6✔
889

890
        private void Add(string name, PropertyInfo property, PropertyKind kind)
891
        {
64✔
892
            if (property.IsIndexer())
64✔
893
            {
2✔
894
                // We explicitly skip indexers
895
            }
2✔
896
            else if (!kindMap.TryGetValue(name, out var existingKind))
62✔
897
            {
54✔
898
                kindMap[name] = kind;
54✔
899
                propertiesWithName.Add((name, property));
54✔
900
            }
54✔
901
            else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
8✔
902
            {
2✔
903
                // Normal properties have priority over explicitly implemented properties
904
                kindMap[name] = kind;
2✔
905

906
                for (int i = 0; i < propertiesWithName.Count; i++)
4!
907
                {
2✔
908
                    if (propertiesWithName[i].Name == name)
2!
909
                    {
2✔
910
                        propertiesWithName[i] = (name, property);
2✔
911
                        return;
2✔
912
                    }
913
                }
×
914

UNCOV
915
                propertiesWithName.Add((name, property));
×
UNCOV
916
            }
×
917
            else
918
            {
6✔
919
                // Property with that name already exists
920
            }
6✔
921
        }
64✔
922
    }
923
}
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