• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

dennisdoomen / reflectify / 22524645215

28 Feb 2026 04:38PM UTC coverage: 96.739% (+0.05%) from 96.693%
22524645215

push

github

dennisdoomen
Add Memory<T> and Span<T> tests for IsStruct and IsRefStruct

Co-authored-by: dennisdoomen <572734+dennisdoomen@users.noreply.github.com>

233 of 244 branches covered (95.49%)

Branch coverage included in aggregate %.

390 of 400 relevant lines covered (97.5%)

73.53 hits per line

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

96.74
/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
    {
24✔
26
        string name = type.Name;
24✔
27
        int index = name.IndexOf('`');
24✔
28
        return index == -1 ? name : name.Substring(0, index);
24✔
29
    }
24✔
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
    {
18✔
36
        // do not consider a type to be derived from itself
37
        if (type == openGenericType)
18✔
38
        {
6✔
39
            return false;
6✔
40
        }
41

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

53
        return false;
6✔
54
    }
18✔
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
    {
24✔
62
        if (type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType)
24!
63
        {
×
64
            return [type];
×
65
        }
66

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

69
        return interfaces
24✔
70
            .Where(t => t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == openGenericType))
30✔
71
            .ToArray();
24✔
72
    }
24✔
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
    {
66✔
81
        return type.IsDefined(typeof(TAttribute), inherit: false);
66✔
82
    }
66✔
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
    {
30✔
91
        if (predicate is null)
30✔
92
        {
6✔
93
            throw new ArgumentNullException(nameof(predicate));
6✔
94
        }
95

96
        return type.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
24✔
97
    }
24✔
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
    {
18✔
106
        return type.IsDefined(typeof(TAttribute), inherit: true);
18✔
107
    }
18✔
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
    {
24✔
117
        if (predicate is null)
24✔
118
        {
12✔
119
            throw new ArgumentNullException(nameof(predicate));
12✔
120
        }
121

122
        return type.GetCustomAttributes<TAttribute>(inherit: true).Any(predicate);
12✔
123
    }
12✔
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
    {
12✔
131
        return (TAttribute[])type.GetCustomAttributes<TAttribute>(inherit: true);
12✔
132
    }
12✔
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
    {
24✔
140
        if (predicate is null)
24!
141
        {
×
142
            throw new ArgumentNullException(nameof(predicate));
×
143
        }
144

145
        return type.GetCustomAttributes<TAttribute>(inherit: true).Where(predicate).ToArray();
24✔
146
    }
24✔
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
    {
12✔
153
        MethodInfo method = type
12✔
154
            .GetMethod("Equals", [typeof(object)]);
12✔
155

156
        return method is not null
12!
157
               && method.GetBaseDefinition().DeclaringType != method.DeclaringType;
12✔
158
    }
12✔
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
    {
36✔
169
        return actualType.IsSameOrInherits(typeof(TExpectedType));
36✔
170
    }
36✔
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
    {
72✔
181
        return actualType == expectedType ||
72✔
182
               expectedType.IsAssignableFrom(actualType) ||
72✔
183
               (actualType.BaseType is { IsGenericType: true } && actualType.BaseType.GetGenericTypeDefinition() == expectedType);
72✔
184
    }
72✔
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
    {
18✔
191
        return typeof(Delegate).IsAssignableFrom(type);
18✔
192
    }
18✔
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
    {
30✔
202
        return type.HasAttribute<CompilerGeneratedAttribute>() ||
30✔
203
               type.IsRecord() ||
30✔
204
               type.IsTuple();
30✔
205
    }
30✔
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
    {
18✔
213
        return !type.IsAnonymous() && !type.IsTuple();
18✔
214
    }
18✔
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
    {
42✔
221
        if (!type.IsGenericType)
42✔
222
        {
18✔
223
            return false;
18✔
224
        }
225

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

231
        return openType == typeof(ValueTuple<>)
8!
232
               || openType == typeof(ValueTuple<,>)
8✔
233
               || openType == typeof(ValueTuple<,,>)
8✔
234
               || openType == typeof(ValueTuple<,,,>)
8✔
235
               || openType == typeof(ValueTuple<,,,,>)
8✔
236
               || openType == typeof(ValueTuple<,,,,,>)
8✔
237
               || openType == typeof(ValueTuple<,,,,,,>)
8✔
238
               || (openType == typeof(ValueTuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]))
8✔
239
               || openType == typeof(Tuple<>)
8✔
240
               || openType == typeof(Tuple<,>)
8✔
241
               || openType == typeof(Tuple<,,>)
8✔
242
               || openType == typeof(Tuple<,,,>)
8✔
243
               || openType == typeof(Tuple<,,,,>)
8✔
244
               || openType == typeof(Tuple<,,,,,>)
8✔
245
               || openType == typeof(Tuple<,,,,,,>)
8✔
246
               || (openType == typeof(Tuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]));
8✔
247
#endif
248
    }
42✔
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
    {
36✔
255
        if (!type.FullName!.Contains("AnonymousType"))
36✔
256
        {
24✔
257
            return false;
24✔
258
        }
259

260
        return type.HasAttribute<CompilerGeneratedAttribute>();
12✔
261
    }
36✔
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
    {
36✔
268
        return type.IsRecordClass() || type.IsRecordStruct();
36✔
269
    }
36✔
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
    {
54✔
276
        return type.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) is { } &&
54!
277
               type.GetProperty("EqualityContract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)?
54✔
278
                   .GetMethod?.HasAttribute<CompilerGeneratedAttribute>() == true;
54✔
279
    }
54✔
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
    {
48✔
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) &&
48!
290
               type.GetMethod("PrintMembers", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, null,
48✔
291
                   [typeof(StringBuilder)], null) is { } &&
48✔
292
               type.GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly, null,
48✔
293
                       [type, type], null)?
48✔
294
                   .HasAttribute<CompilerGeneratedAttribute>() == true;
48✔
295
    }
48✔
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
    {
42✔
304
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
42✔
305
    }
42✔
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
    {
22✔
313
        return type.IsValueType && !type.IsEnum;
22✔
314
    }
22✔
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
    {
20✔
324
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
325
        return type.IsDefined(typeof(System.Runtime.CompilerServices.IsByRefLikeAttribute), false);
16✔
326
#else
327
        return false;
4✔
328
#endif
329
    }
20✔
330
}
331

332
internal static class TypeMemberExtensions
333
{
334
    private static readonly ConcurrentDictionary<(Type Type, MemberKind Kind), Reflector> ReflectorCache = new();
6✔
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
    {
152✔
343
        return GetFor(type, kind).Properties;
152✔
344
    }
152✔
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
    {
94✔
357
        if (propertyName is null or "")
94✔
358
        {
12✔
359
            throw new ArgumentException("The property name cannot be null or empty", nameof(propertyName));
12✔
360
        }
361

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

364
        return Array.Find(properties, p =>
82✔
365
            p.Name == propertyName || p.Name.EndsWith("." + propertyName, StringComparison.Ordinal));
236✔
366
    }
82✔
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
    {
66✔
375
        return GetFor(type, kind).Fields;
66✔
376
    }
66✔
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
    {
54✔
386
        if (fieldName is null or "")
54✔
387
        {
12✔
388
            throw new ArgumentException("The field name cannot be null or empty", nameof(fieldName));
12✔
389
        }
390

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

393
        return Array.Find(fields, p => p.Name == fieldName);
84✔
394
    }
42✔
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
    {
6✔
403
        return GetFor(type, kind).Members;
6✔
404
    }
6✔
405

406
    private static Reflector GetFor(Type typeToReflect, MemberKind kind)
407
    {
224✔
408
        return ReflectorCache.GetOrAdd((typeToReflect, kind),
224✔
409
            static key => new Reflector(key.Type, key.Kind));
306✔
410
    }
224✔
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
    {
84✔
422
        if (methodName is null or "")
84✔
423
        {
12✔
424
            throw new ArgumentException("The method name cannot be null or empty", nameof(methodName));
12✔
425
        }
426

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

429
        return type
72✔
430
            .GetMethods(flags)
72✔
431
            .SingleOrDefault(m => m.Name == methodName && HasSameParameters(parameterTypes, m));
540✔
432
    }
72✔
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
    {
6✔
439
        return type.FindMethod(methodName, memberKind);
6✔
440
    }
6✔
441

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

450
        return method.GetParameters()
24✔
451
            .Select(p => p.ParameterType)
66✔
452
            .SequenceEqual(parameterTypes);
24✔
453
    }
60✔
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
    {
6✔
462
        return type.FindMethod(methodName, memberKind, parameterTypes) is not null;
6✔
463
    }
6✔
464

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

469
        return type.GetProperties(flags)
18✔
470
            .SingleOrDefault(p =>
18✔
471
                p.IsIndexer() && p.GetIndexParameters().Select(i => i.ParameterType).SequenceEqual(parameterTypes));
72✔
472
    }
18✔
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
    {
12✔
482
        return type
12✔
483
            .GetConversionOperators(sourceType, targetType, name => name is "op_Explicit")
6✔
484
            .SingleOrDefault();
12✔
485
    }
12✔
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
    {
12✔
493
        return type
12✔
494
            .GetConversionOperators(sourceType, targetType, name => name is "op_Implicit")
6✔
495
            .SingleOrDefault();
12✔
496
    }
12✔
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
    {
24✔
502
        return type
24✔
503
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
24✔
504
            .Where(m =>
24✔
505
                m.IsPublic
72✔
506
                && m.IsStatic
72✔
507
                && m.IsSpecialName
72✔
508
                && m.ReturnType == targetType
72✔
509
                && predicate(m.Name)
72✔
510
                && m.GetParameters() is { Length: 1 } parameters
72✔
511
                && parameters[0].ParameterType == sourceType);
72✔
512
    }
24✔
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
    {
18✔
522
        if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
18✔
523
        {
6✔
524
            type = type.GetGenericArguments()[0];
6✔
525
        }
6✔
526

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

531
internal static class MemberInfoExtensions
532
{
533
    public static bool HasAttribute<TAttribute>(this MemberInfo member)
534
        where TAttribute : Attribute
535
    {
30✔
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);
30✔
540
    }
30✔
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
    {
18✔
548
        if (predicate is null)
18✔
549
        {
6✔
550
            throw new ArgumentNullException(nameof(predicate));
6✔
551
        }
552

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

556
    public static bool HasAttributeInHierarchy<TAttribute>(this MemberInfo member)
557
        where TAttribute : Attribute
558
    {
×
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);
×
563
    }
×
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
    {
12✔
575
        return parameter.IsDefined(typeof(TAttribute), inherit: false);
12✔
576
    }
12✔
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
    {
18✔
585
        if (predicate is null)
18✔
586
        {
6✔
587
            throw new ArgumentNullException(nameof(predicate));
6✔
588
        }
589

590
        return parameter.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
12✔
591
    }
12✔
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
    {
12✔
600
        return parameter.IsDefined(typeof(TAttribute), inherit: true);
12✔
601
    }
12✔
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
    {
228✔
611
        return member.GetIndexParameters().Length != 0;
228✔
612
    }
228✔
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
    {
262✔
620
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
621
        return prop.Name.Contains('.');
176✔
622
#else
623
        return prop.Name.IndexOf('.') != -1;
86✔
624
#endif
625
    }
262✔
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
    {
196✔
632
        return prop.GetMethod is { IsPublic: true } || prop.SetMethod is { IsPublic: true };
196✔
633
    }
196✔
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
    {
60✔
641
        return prop.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true } ||
60!
642
               prop.SetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
60✔
643
    }
60✔
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
    {
40✔
651
        return prop.GetMethod is { IsAbstract: true } || prop.SetMethod is { IsAbstract: true };
40✔
652
    }
40✔
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
    {
90✔
673
        BindingFlags flags = BindingFlags.Default;
90✔
674

675
        if (kind.HasFlag(MemberKind.Public))
90✔
676
        {
72✔
677
            flags |= BindingFlags.Public;
72✔
678
        }
72✔
679

680
        if (kind.HasFlag(MemberKind.Internal))
90✔
681
        {
18✔
682
            flags |= BindingFlags.NonPublic;
18✔
683
        }
18✔
684

685
        return flags;
90✔
686
    }
90✔
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
693
{
694
    private readonly List<FieldInfo> selectedFields = new();
82✔
695
    private readonly OrderedPropertyCollection selectedProperties = new();
82✔
696

697
    public Reflector(Type typeToReflect, MemberKind kind)
82✔
698
    {
82✔
699
        LoadProperties(typeToReflect, kind);
82✔
700
        LoadFields(typeToReflect, kind);
82✔
701

702
        Members = [.. selectedProperties, .. selectedFields];
82✔
703
    }
82✔
704

705
    private void LoadProperties(Type typeToReflect, MemberKind kind)
706
    {
82✔
707
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
708
        {
128✔
709
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
710
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
711

712
            var allProperties = typeToReflect.GetProperties(flags);
128✔
713

714
            AddNormalProperties(kind, allProperties);
128✔
715

716
            AddExplicitlyImplementedProperties(kind, allProperties);
128✔
717

718
            AddInterfaceProperties(typeToReflect, kind, flags);
128✔
719

720
            // Move to the base type
721
            typeToReflect = typeToReflect.BaseType;
128✔
722
        }
128✔
723
    }
82✔
724

725
    private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
726
    {
128✔
727
        if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) ||
128✔
728
            kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
729
        {
114✔
730
            foreach (var property in allProperties)
902✔
731
            {
280✔
732
                if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
280✔
733
                {
148✔
734
                    selectedProperties.AddNormal(property);
148✔
735
                }
148✔
736
            }
280✔
737
        }
114✔
738
    }
128✔
739

740
    private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
741
    {
280✔
742
        return (kind.HasFlag(MemberKind.Public) && prop.IsPublic()) ||
280✔
743
               (kind.HasFlag(MemberKind.Internal) && prop.IsInternal());
280✔
744
    }
280✔
745

746
    private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
747
    {
128✔
748
        if (kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
749
        {
48✔
750
            foreach (var property in allProperties)
372✔
751
            {
114✔
752
                if (property.IsExplicitlyImplemented())
114✔
753
                {
24✔
754
                    selectedProperties.AddExplicitlyImplemented(property);
24✔
755
                }
24✔
756
            }
114✔
757
        }
48✔
758
    }
128✔
759

760
#pragma warning disable AV1561
761
    private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
762
    {
128✔
763
        if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface)
128✔
764
        {
32✔
765
            var interfaces = typeToReflect.GetInterfaces();
32✔
766

767
            foreach (var interfaceType in interfaces)
160✔
768
            {
32✔
769
                foreach (var prop in interfaceType.GetProperties(flags))
176✔
770
                {
40✔
771
                    if (!prop.IsAbstract() || typeToReflect.IsInterface)
40✔
772
                    {
14✔
773
                        selectedProperties.AddFromInterface(prop);
14✔
774
                    }
14✔
775
                }
40✔
776
            }
32✔
777
        }
32✔
778
    }
128✔
779
#pragma warning restore AV1561
780

781
    private void LoadFields(Type typeToReflect, MemberKind kind)
782
    {
82✔
783
        var collectedFieldNames = new HashSet<string>();
82✔
784

785
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
786
        {
128✔
787
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
788
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
789

790
            var files = typeToReflect.GetFields(flags);
128✔
791

792
            foreach (var field in files)
1,172✔
793
            {
394✔
794
                if (HasVisibility(kind, field) && collectedFieldNames.Add(field.Name))
394✔
795
                {
48✔
796
                    selectedFields.Add(field);
48✔
797
                }
48✔
798
            }
394✔
799

800
            // Move to the base type
801
            typeToReflect = typeToReflect.BaseType;
128✔
802
        }
128✔
803
    }
82✔
804

805
    private static bool HasVisibility(MemberKind kind, FieldInfo field)
806
    {
394✔
807
        return (kind.HasFlag(MemberKind.Public) && field.IsPublic) ||
394✔
808
               (kind.HasFlag(MemberKind.Internal) && (field.IsAssembly || field.IsFamilyOrAssembly));
394✔
809
    }
394✔
810

811
    public MemberInfo[] Members { get; }
6✔
812

813
    public PropertyInfo[] Properties => selectedProperties.ToArray();
152✔
814

815
    public FieldInfo[] Fields => selectedFields.ToArray();
66✔
816

817
    private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
818
    {
819
        private readonly Dictionary<string, PropertyKind> kindMap = new();
82✔
820
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
82✔
821

822
        public IEnumerator<PropertyInfo> GetEnumerator()
823
        {
234✔
824
            return propertiesWithName.Select(x => x.Property).GetEnumerator();
676✔
825
        }
234✔
826

827
        IEnumerator IEnumerable.GetEnumerator()
828
        {
×
829
            return GetEnumerator();
×
830
        }
×
831

832
        private enum PropertyKind
833
        {
834
            Normal,
835
            ExplicitlyImplemented,
836
            Interface
837
        }
838

839
        public void AddExplicitlyImplemented(PropertyInfo property)
840
        {
24✔
841
            var name = property.Name.Split('.').Last();
24✔
842

843
            Add(name, property, PropertyKind.ExplicitlyImplemented);
24✔
844
        }
24✔
845

846
        public void AddNormal(PropertyInfo property)
847
        {
148✔
848
            Add(property.Name, property, PropertyKind.Normal);
148✔
849
        }
148✔
850

851
        public void AddFromInterface(PropertyInfo property)
852
        {
14✔
853
            Add(property.Name, property, PropertyKind.Interface);
14✔
854
        }
14✔
855

856
        private void Add(string name, PropertyInfo property, PropertyKind kind)
857
        {
186✔
858
            if (property.IsIndexer())
186✔
859
            {
6✔
860
                // We explicitly skip indexers
861
            }
6✔
862
            else if (!kindMap.TryGetValue(name, out var existingKind))
180✔
863
            {
150✔
864
                kindMap[name] = kind;
150✔
865
                propertiesWithName.Add((name, property));
150✔
866
            }
150✔
867
            else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
30✔
868
            {
6✔
869
                // Normal properties have priority over interface properties
870
                kindMap[name] = kind;
6✔
871
                propertiesWithName.RemoveAll(x => x.Name == name);
12✔
872
                propertiesWithName.Add((name, property));
6✔
873
            }
6✔
874
            else
875
            {
24✔
876
            // Property with that name already exists
877
        }
24✔
878
    }
186✔
879
}
880
}
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