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

dennisdoomen / reflectify / 22524620084

28 Feb 2026 04:37PM UTC coverage: 96.693% (+0.08%) from 96.618%
22524620084

push

github

dennisdoomen
Add HasAttributeInHierarchy extension method for ParameterInfo

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

231 of 242 branches covered (95.45%)

Branch coverage included in aggregate %.

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

3 existing lines in 1 file now uncovered.

383 of 393 relevant lines covered (97.46%)

74.52 hits per line

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

96.69
/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

308
internal static class TypeMemberExtensions
309
{
310
    private static readonly ConcurrentDictionary<(Type Type, MemberKind Kind), Reflector> ReflectorCache = new();
6✔
311

312
    /// <summary>
313
    /// Gets the public, internal, explicitly implemented and/or default properties of a type hierarchy.
314
    /// </summary>
315
    /// <param name="type">The type to reflect.</param>
316
    /// <param name="kind">The kind of properties to include in the response.</param>
317
    public static PropertyInfo[] GetProperties(this Type type, MemberKind kind)
318
    {
152✔
319
        return GetFor(type, kind).Properties;
152✔
320
    }
152✔
321

322
    /// <summary>
323
    /// Finds the property by a case-sensitive name and with a certain visibility.
324
    /// </summary>
325
    /// <remarks>
326
    /// Normal property get priority over explicitly implemented properties and default interface properties.
327
    /// </remarks>
328
    /// <returns>
329
    /// Returns <see langword="null" /> if no such property exists.
330
    /// </returns>
331
    public static PropertyInfo FindProperty(this Type type, string propertyName, MemberKind memberVisibility)
332
    {
94✔
333
        if (propertyName is null or "")
94✔
334
        {
12✔
335
            throw new ArgumentException("The property name cannot be null or empty", nameof(propertyName));
12✔
336
        }
337

338
        var properties = type.GetProperties(memberVisibility);
82✔
339

340
        return Array.Find(properties, p =>
82✔
341
            p.Name == propertyName || p.Name.EndsWith("." + propertyName, StringComparison.Ordinal));
236✔
342
    }
82✔
343

344
    /// <summary>
345
    /// Gets the public and/or internal fields of a type hierarchy.
346
    /// </summary>
347
    /// <param name="type">The type to reflect.</param>
348
    /// <param name="kind">The kind of fields to include in the response.</param>
349
    public static FieldInfo[] GetFields(this Type type, MemberKind kind)
350
    {
66✔
351
        return GetFor(type, kind).Fields;
66✔
352
    }
66✔
353

354
    /// <summary>
355
    /// Finds the field by a case-sensitive name.
356
    /// </summary>
357
    /// <returns>
358
    /// Returns <see langword="null" /> if no such field exists.
359
    /// </returns>
360
    public static FieldInfo FindField(this Type type, string fieldName, MemberKind memberVisibility)
361
    {
54✔
362
        if (fieldName is null or "")
54✔
363
        {
12✔
364
            throw new ArgumentException("The field name cannot be null or empty", nameof(fieldName));
12✔
365
        }
366

367
        var fields = type.GetFields(memberVisibility);
42✔
368

369
        return Array.Find(fields, p => p.Name == fieldName);
84✔
370
    }
42✔
371

372
    /// <summary>
373
    /// Gets a combination of <see cref="GetProperties"/> and <see cref="GetFields"/>.
374
    /// </summary>
375
    /// <param name="type">The type to reflect.</param>
376
    /// <param name="kind">The kind of fields and properties to include in the response.</param>
377
    public static MemberInfo[] GetMembers(this Type type, MemberKind kind)
378
    {
6✔
379
        return GetFor(type, kind).Members;
6✔
380
    }
6✔
381

382
    private static Reflector GetFor(Type typeToReflect, MemberKind kind)
383
    {
224✔
384
        return ReflectorCache.GetOrAdd((typeToReflect, kind),
224✔
385
            static key => new Reflector(key.Type, key.Kind));
306✔
386
    }
224✔
387

388
    /// <summary>
389
    /// Finds a method by its name, parameter types and visibility. Returns <see langword="null" /> if no such method exists.
390
    /// </summary>
391
    /// <remarks>
392
    /// If you don't specify any parameter types, the method will be found by its name only.
393
    /// </remarks>
394
#pragma warning disable AV1561
395
    public static MethodInfo FindMethod(this Type type, string methodName, MemberKind kind, params Type[] parameterTypes)
396
#pragma warning restore AV1561
397
    {
84✔
398
        if (methodName is null or "")
84✔
399
        {
12✔
400
            throw new ArgumentException("The method name cannot be null or empty", nameof(methodName));
12✔
401
        }
402

403
        var flags = kind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
72✔
404

405
        return type
72✔
406
            .GetMethods(flags)
72✔
407
            .SingleOrDefault(m => m.Name == methodName && HasSameParameters(parameterTypes, m));
540✔
408
    }
72✔
409

410
    /// <summary>
411
    /// Finds a parameterless method by its name and visibility. Returns <see langword="null" /> if no such method exists.
412
    /// </summary>
413
    public static MethodInfo FindParameterlessMethod(this Type type, string methodName, MemberKind memberKind)
414
    {
6✔
415
        return type.FindMethod(methodName, memberKind);
6✔
416
    }
6✔
417

418
    private static bool HasSameParameters(Type[] parameterTypes, MethodInfo method)
419
    {
60✔
420
        if (parameterTypes.Length == 0)
60✔
421
        {
36✔
422
            // If we don't specify any specific parameters, it matches always.
423
            return true;
36✔
424
        }
425

426
        return method.GetParameters()
24✔
427
            .Select(p => p.ParameterType)
66✔
428
            .SequenceEqual(parameterTypes);
24✔
429
    }
60✔
430

431
    /// <summary>
432
    /// Determines whether the type has a method with the specified name and visibility.
433
    /// </summary>
434
#pragma warning disable AV1561
435
    public static bool HasMethod(this Type type, string methodName, MemberKind memberKind, params Type[] parameterTypes)
436
#pragma warning restore AV1561
437
    {
6✔
438
        return type.FindMethod(methodName, memberKind, parameterTypes) is not null;
6✔
439
    }
6✔
440

441
    public static PropertyInfo FindIndexer(this Type type, MemberKind memberKind, params Type[] parameterTypes)
442
    {
18✔
443
        var flags = memberKind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
18✔
444

445
        return type.GetProperties(flags)
18✔
446
            .SingleOrDefault(p =>
18✔
447
                p.IsIndexer() && p.GetIndexParameters().Select(i => i.ParameterType).SequenceEqual(parameterTypes));
72✔
448
    }
18✔
449

450
#pragma warning disable AV1561
451

452
    /// <summary>
453
    /// Finds an explicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
454
    /// Returns <see langword="null" /> if no such operator exists.
455
    /// </summary>
456
    public static MethodInfo FindExplicitConversionOperator(this Type type, Type sourceType, Type targetType)
457
    {
12✔
458
        return type
12✔
459
            .GetConversionOperators(sourceType, targetType, name => name is "op_Explicit")
6✔
460
            .SingleOrDefault();
12✔
461
    }
12✔
462

463
    /// <summary>
464
    /// Finds an implicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
465
    /// Returns <see langword="null" /> if no such operator exists.
466
    /// </summary>
467
    public static MethodInfo FindImplicitConversionOperator(this Type type, Type sourceType, Type targetType)
468
    {
12✔
469
        return type
12✔
470
            .GetConversionOperators(sourceType, targetType, name => name is "op_Implicit")
6✔
471
            .SingleOrDefault();
12✔
472
    }
12✔
473

474
    private static IEnumerable<MethodInfo> GetConversionOperators(this Type type, Type sourceType, Type targetType,
475
#pragma warning restore AV1561
476
        Func<string, bool> predicate)
477
    {
24✔
478
        return type
24✔
479
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
24✔
480
            .Where(m =>
24✔
481
                m.IsPublic
72✔
482
                && m.IsStatic
72✔
483
                && m.IsSpecialName
72✔
484
                && m.ReturnType == targetType
72✔
485
                && predicate(m.Name)
72✔
486
                && m.GetParameters() is { Length: 1 } parameters
72✔
487
                && parameters[0].ParameterType == sourceType);
72✔
488
    }
24✔
489
}
490

491
internal static class TypeExtensions
492
{
493
    /// <summary>
494
    /// If the type provided is a nullable type, gets the underlying type. Returns the type itself otherwise.
495
    /// </summary>
496
    public static Type NullableOrActualType(this Type type)
497
    {
18✔
498
        if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
18✔
499
        {
6✔
500
            type = type.GetGenericArguments()[0];
6✔
501
        }
6✔
502

503
        return type;
18✔
504
    }
18✔
505
}
506

507
internal static class MemberInfoExtensions
508
{
509
    public static bool HasAttribute<TAttribute>(this MemberInfo member)
510
        where TAttribute : Attribute
511
    {
30✔
512
        // Do not use MemberInfo.IsDefined
513
        // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work.
514
        // https://github.com/dotnet/runtime/issues/30219
515
        return Attribute.IsDefined(member, typeof(TAttribute), inherit: false);
30✔
516
    }
30✔
517

518
    /// <summary>
519
    /// Determines whether the member has an attribute of the specified type that matches the predicate.
520
    /// </summary>
521
    public static bool HasAttribute<TAttribute>(this MemberInfo member, Func<TAttribute, bool> predicate)
522
        where TAttribute : Attribute
523
    {
18✔
524
        if (predicate is null)
18✔
525
        {
6✔
526
            throw new ArgumentNullException(nameof(predicate));
6✔
527
        }
528

529
        return member.GetCustomAttributes<TAttribute>().Any(a => predicate(a));
24✔
530
    }
12✔
531

532
    public static bool HasAttributeInHierarchy<TAttribute>(this MemberInfo member)
533
        where TAttribute : Attribute
534
    {
×
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: true);
×
539
    }
×
540
}
541

542
internal static class ParameterInfoExtensions
543
{
544
    /// <summary>
545
    /// Returns <see langword="true" /> if the parameter is decorated with the specific <typeparamref name="TAttribute"/>,
546
    /// or <see langword="false" /> otherwise.
547
    /// </summary>
548
    public static bool HasAttribute<TAttribute>(this ParameterInfo parameter)
549
        where TAttribute : Attribute
550
    {
12✔
551
        return parameter.IsDefined(typeof(TAttribute), inherit: false);
12✔
552
    }
12✔
553

554
    /// <summary>
555
    /// Returns <see langword="true" /> if the parameter is decorated with the specific <typeparamref name="TAttribute"/> <i>and</i>
556
    /// that attribute instance matches the predicate, or <see langword="false" /> otherwise.
557
    /// </summary>
558
    public static bool HasAttribute<TAttribute>(this ParameterInfo parameter, Func<TAttribute, bool> predicate)
559
        where TAttribute : Attribute
560
    {
18✔
561
        if (predicate is null)
18✔
562
        {
6✔
563
            throw new ArgumentNullException(nameof(predicate));
6✔
564
        }
565

566
        return parameter.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
12✔
567
    }
12✔
568

569
    /// <summary>
570
    /// Returns <see langword="true" /> if the parameter or one of its overridden definitions are decorated with the
571
    /// specific <typeparamref name="TAttribute"/>.
572
    /// </summary>
573
    public static bool HasAttributeInHierarchy<TAttribute>(this ParameterInfo parameter)
574
        where TAttribute : Attribute
575
    {
12✔
576
        return parameter.IsDefined(typeof(TAttribute), inherit: true);
12✔
577
    }
12✔
578
}
579

580
internal static class PropertyInfoExtensions
581
{
582
    /// <summary>
583
    /// Returns <see langword="true" /> if the property is an indexer, or <see langword="false" /> otherwise.
584
    /// </summary>
585
    public static bool IsIndexer(this PropertyInfo member)
586
    {
228✔
587
        return member.GetIndexParameters().Length != 0;
228✔
588
    }
228✔
589

590
    /// <summary>
591
    /// Returns <see langword="true" /> if the property is explicitly implemented on the
592
    /// <see cref="MemberInfo.DeclaringType"/>, or <see langword="false" /> otherwise.
593
    /// </summary>
594
    public static bool IsExplicitlyImplemented(this PropertyInfo prop)
595
    {
262✔
596
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
597
        return prop.Name.Contains('.');
176✔
598
#else
599
        return prop.Name.IndexOf('.') != -1;
86✔
600
#endif
601
    }
262✔
602

603
    /// <summary>
604
    /// Returns <see langword="true" /> if the property has a public getter or setter, or <see langword="false" /> otherwise.
605
    /// </summary>
606
    public static bool IsPublic(this PropertyInfo prop)
607
    {
196✔
608
        return prop.GetMethod is { IsPublic: true } || prop.SetMethod is { IsPublic: true };
196✔
609
    }
196✔
610

611
    /// <summary>
612
    /// Returns <see langword="true" /> if the property is internal, or <see langword="false" /> otherwise.
613
    /// </summary>
614
    /// <param name="prop">The property to check.</param>
615
    public static bool IsInternal(this PropertyInfo prop)
616
    {
60✔
617
        return prop.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true } ||
60!
618
               prop.SetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
60✔
619
    }
60✔
620

621
    /// <summary>
622
    /// Returns <see langword="true" /> if the property is abstract, or <see langword="false" /> otherwise.
623
    /// </summary>
624
    /// <param name="prop">The property to check.</param>
625
    public static bool IsAbstract(this PropertyInfo prop)
626
    {
40✔
627
        return prop.GetMethod is { IsAbstract: true } || prop.SetMethod is { IsAbstract: true };
40✔
628
    }
40✔
629
}
630

631
/// <summary>
632
/// Defines the kinds of members you want to get when querying for the fields and properties of a type.
633
/// </summary>
634
[Flags]
635
internal enum MemberKind
636
{
637
    None,
638
    Public = 1,
639
    Internal = 2,
640
    ExplicitlyImplemented = 4,
641
    DefaultInterfaceProperties = 8,
642
    Static = 16
643
}
644

645
internal static class MemberKindExtensions
646
{
647
    public static BindingFlags ToBindingFlags(this MemberKind kind)
648
    {
90✔
649
        BindingFlags flags = BindingFlags.Default;
90✔
650

651
        if (kind.HasFlag(MemberKind.Public))
90✔
652
        {
72✔
653
            flags |= BindingFlags.Public;
72✔
654
        }
72✔
655

656
        if (kind.HasFlag(MemberKind.Internal))
90✔
657
        {
18✔
658
            flags |= BindingFlags.NonPublic;
18✔
659
        }
18✔
660

661
        return flags;
90✔
662
    }
90✔
663
}
664

665
/// <summary>
666
/// Helper class to get all the public and internal fields and properties from a type.
667
/// </summary>
668
internal sealed class Reflector
669
{
670
    private readonly List<FieldInfo> selectedFields = new();
82✔
671
    private readonly OrderedPropertyCollection selectedProperties = new();
82✔
672

673
    public Reflector(Type typeToReflect, MemberKind kind)
82✔
674
    {
82✔
675
        LoadProperties(typeToReflect, kind);
82✔
676
        LoadFields(typeToReflect, kind);
82✔
677

678
        Members = [.. selectedProperties, .. selectedFields];
82✔
679
    }
82✔
680

681
    private void LoadProperties(Type typeToReflect, MemberKind kind)
682
    {
82✔
683
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
684
        {
128✔
685
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
686
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
687

688
            var allProperties = typeToReflect.GetProperties(flags);
128✔
689

690
            AddNormalProperties(kind, allProperties);
128✔
691

692
            AddExplicitlyImplementedProperties(kind, allProperties);
128✔
693

694
            AddInterfaceProperties(typeToReflect, kind, flags);
128✔
695

696
            // Move to the base type
697
            typeToReflect = typeToReflect.BaseType;
128✔
698
        }
128✔
699
    }
82✔
700

701
    private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
702
    {
128✔
703
        if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) ||
128✔
704
            kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
705
        {
114✔
706
            foreach (var property in allProperties)
902✔
707
            {
280✔
708
                if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
280✔
709
                {
148✔
710
                    selectedProperties.AddNormal(property);
148✔
711
                }
148✔
712
            }
280✔
713
        }
114✔
714
    }
128✔
715

716
    private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
717
    {
280✔
718
        return (kind.HasFlag(MemberKind.Public) && prop.IsPublic()) ||
280✔
719
               (kind.HasFlag(MemberKind.Internal) && prop.IsInternal());
280✔
720
    }
280✔
721

722
    private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
723
    {
128✔
724
        if (kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
725
        {
48✔
726
            foreach (var property in allProperties)
372✔
727
            {
114✔
728
                if (property.IsExplicitlyImplemented())
114✔
729
                {
24✔
730
                    selectedProperties.AddExplicitlyImplemented(property);
24✔
731
                }
24✔
732
            }
114✔
733
        }
48✔
734
    }
128✔
735

736
#pragma warning disable AV1561
737
    private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
738
    {
128✔
739
        if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface)
128✔
740
        {
32✔
741
            var interfaces = typeToReflect.GetInterfaces();
32✔
742

743
            foreach (var interfaceType in interfaces)
160✔
744
            {
32✔
745
                foreach (var prop in interfaceType.GetProperties(flags))
176✔
746
                {
40✔
747
                    if (!prop.IsAbstract() || typeToReflect.IsInterface)
40✔
748
                    {
14✔
749
                        selectedProperties.AddFromInterface(prop);
14✔
750
                    }
14✔
751
                }
40✔
752
            }
32✔
753
        }
32✔
754
    }
128✔
755
#pragma warning restore AV1561
756

757
    private void LoadFields(Type typeToReflect, MemberKind kind)
758
    {
82✔
759
        var collectedFieldNames = new HashSet<string>();
82✔
760

761
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
762
        {
128✔
763
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
764
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
765

766
            var files = typeToReflect.GetFields(flags);
128✔
767

768
            foreach (var field in files)
1,172✔
769
            {
394✔
770
                if (HasVisibility(kind, field) && collectedFieldNames.Add(field.Name))
394✔
771
                {
48✔
772
                    selectedFields.Add(field);
48✔
773
                }
48✔
774
            }
394✔
775

776
            // Move to the base type
777
            typeToReflect = typeToReflect.BaseType;
128✔
778
        }
128✔
779
    }
82✔
780

781
    private static bool HasVisibility(MemberKind kind, FieldInfo field)
782
    {
394✔
783
        return (kind.HasFlag(MemberKind.Public) && field.IsPublic) ||
394✔
784
               (kind.HasFlag(MemberKind.Internal) && (field.IsAssembly || field.IsFamilyOrAssembly));
394✔
785
    }
394✔
786

787
    public MemberInfo[] Members { get; }
6✔
788

789
    public PropertyInfo[] Properties => selectedProperties.ToArray();
152✔
790

791
    public FieldInfo[] Fields => selectedFields.ToArray();
66✔
792

793
    private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
794
    {
795
        private readonly Dictionary<string, PropertyKind> kindMap = new();
82✔
796
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
82✔
797

798
        public IEnumerator<PropertyInfo> GetEnumerator()
799
        {
234✔
800
            return propertiesWithName.Select(x => x.Property).GetEnumerator();
676✔
801
        }
234✔
802

803
        IEnumerator IEnumerable.GetEnumerator()
UNCOV
804
        {
×
UNCOV
805
            return GetEnumerator();
×
UNCOV
806
        }
×
807

808
        private enum PropertyKind
809
        {
810
            Normal,
811
            ExplicitlyImplemented,
812
            Interface
813
        }
814

815
        public void AddExplicitlyImplemented(PropertyInfo property)
816
        {
24✔
817
            var name = property.Name.Split('.').Last();
24✔
818

819
            Add(name, property, PropertyKind.ExplicitlyImplemented);
24✔
820
        }
24✔
821

822
        public void AddNormal(PropertyInfo property)
823
        {
148✔
824
            Add(property.Name, property, PropertyKind.Normal);
148✔
825
        }
148✔
826

827
        public void AddFromInterface(PropertyInfo property)
828
        {
14✔
829
            Add(property.Name, property, PropertyKind.Interface);
14✔
830
        }
14✔
831

832
        private void Add(string name, PropertyInfo property, PropertyKind kind)
833
        {
186✔
834
            if (property.IsIndexer())
186✔
835
            {
6✔
836
                // We explicitly skip indexers
837
            }
6✔
838
            else if (!kindMap.TryGetValue(name, out var existingKind))
180✔
839
            {
150✔
840
                kindMap[name] = kind;
150✔
841
                propertiesWithName.Add((name, property));
150✔
842
            }
150✔
843
            else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
30✔
844
            {
6✔
845
                // Normal properties have priority over interface properties
846
                kindMap[name] = kind;
6✔
847
                propertiesWithName.RemoveAll(x => x.Name == name);
12✔
848
                propertiesWithName.Add((name, property));
6✔
849
            }
6✔
850
            else
851
            {
24✔
852
            // Property with that name already exists
853
        }
24✔
854
    }
186✔
855
}
856
}
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