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

dennisdoomen / reflectify / 22524475328

28 Feb 2026 04:28PM UTC coverage: 96.677% (+0.06%) from 96.618%
22524475328

Pull #132

github

web-flow
Merge 4df03aac1 into c7c37a121
Pull Request #132: Add HasAttribute extension methods for ParameterInfo

231 of 242 branches covered (95.45%)

Branch coverage included in aggregate %.

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

3 existing lines in 1 file now uncovered.

380 of 390 relevant lines covered (97.44%)

75.0 hits per line

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

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

570
internal static class PropertyInfoExtensions
571
{
572
    /// <summary>
573
    /// Returns <see langword="true" /> if the property is an indexer, or <see langword="false" /> otherwise.
574
    /// </summary>
575
    public static bool IsIndexer(this PropertyInfo member)
576
    {
228✔
577
        return member.GetIndexParameters().Length != 0;
228✔
578
    }
228✔
579

580
    /// <summary>
581
    /// Returns <see langword="true" /> if the property is explicitly implemented on the
582
    /// <see cref="MemberInfo.DeclaringType"/>, or <see langword="false" /> otherwise.
583
    /// </summary>
584
    public static bool IsExplicitlyImplemented(this PropertyInfo prop)
585
    {
262✔
586
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
587
        return prop.Name.Contains('.');
176✔
588
#else
589
        return prop.Name.IndexOf('.') != -1;
86✔
590
#endif
591
    }
262✔
592

593
    /// <summary>
594
    /// Returns <see langword="true" /> if the property has a public getter or setter, or <see langword="false" /> otherwise.
595
    /// </summary>
596
    public static bool IsPublic(this PropertyInfo prop)
597
    {
196✔
598
        return prop.GetMethod is { IsPublic: true } || prop.SetMethod is { IsPublic: true };
196✔
599
    }
196✔
600

601
    /// <summary>
602
    /// Returns <see langword="true" /> if the property is internal, or <see langword="false" /> otherwise.
603
    /// </summary>
604
    /// <param name="prop">The property to check.</param>
605
    public static bool IsInternal(this PropertyInfo prop)
606
    {
60✔
607
        return prop.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true } ||
60!
608
               prop.SetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
60✔
609
    }
60✔
610

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

621
/// <summary>
622
/// Defines the kinds of members you want to get when querying for the fields and properties of a type.
623
/// </summary>
624
[Flags]
625
internal enum MemberKind
626
{
627
    None,
628
    Public = 1,
629
    Internal = 2,
630
    ExplicitlyImplemented = 4,
631
    DefaultInterfaceProperties = 8,
632
    Static = 16
633
}
634

635
internal static class MemberKindExtensions
636
{
637
    public static BindingFlags ToBindingFlags(this MemberKind kind)
638
    {
90✔
639
        BindingFlags flags = BindingFlags.Default;
90✔
640

641
        if (kind.HasFlag(MemberKind.Public))
90✔
642
        {
72✔
643
            flags |= BindingFlags.Public;
72✔
644
        }
72✔
645

646
        if (kind.HasFlag(MemberKind.Internal))
90✔
647
        {
18✔
648
            flags |= BindingFlags.NonPublic;
18✔
649
        }
18✔
650

651
        return flags;
90✔
652
    }
90✔
653
}
654

655
/// <summary>
656
/// Helper class to get all the public and internal fields and properties from a type.
657
/// </summary>
658
internal sealed class Reflector
659
{
660
    private readonly List<FieldInfo> selectedFields = new();
82✔
661
    private readonly OrderedPropertyCollection selectedProperties = new();
82✔
662

663
    public Reflector(Type typeToReflect, MemberKind kind)
82✔
664
    {
82✔
665
        LoadProperties(typeToReflect, kind);
82✔
666
        LoadFields(typeToReflect, kind);
82✔
667

668
        Members = [.. selectedProperties, .. selectedFields];
82✔
669
    }
82✔
670

671
    private void LoadProperties(Type typeToReflect, MemberKind kind)
672
    {
82✔
673
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
674
        {
128✔
675
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
676
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
677

678
            var allProperties = typeToReflect.GetProperties(flags);
128✔
679

680
            AddNormalProperties(kind, allProperties);
128✔
681

682
            AddExplicitlyImplementedProperties(kind, allProperties);
128✔
683

684
            AddInterfaceProperties(typeToReflect, kind, flags);
128✔
685

686
            // Move to the base type
687
            typeToReflect = typeToReflect.BaseType;
128✔
688
        }
128✔
689
    }
82✔
690

691
    private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
692
    {
128✔
693
        if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) ||
128✔
694
            kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
695
        {
114✔
696
            foreach (var property in allProperties)
902✔
697
            {
280✔
698
                if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
280✔
699
                {
148✔
700
                    selectedProperties.AddNormal(property);
148✔
701
                }
148✔
702
            }
280✔
703
        }
114✔
704
    }
128✔
705

706
    private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
707
    {
280✔
708
        return (kind.HasFlag(MemberKind.Public) && prop.IsPublic()) ||
280✔
709
               (kind.HasFlag(MemberKind.Internal) && prop.IsInternal());
280✔
710
    }
280✔
711

712
    private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
713
    {
128✔
714
        if (kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
715
        {
48✔
716
            foreach (var property in allProperties)
372✔
717
            {
114✔
718
                if (property.IsExplicitlyImplemented())
114✔
719
                {
24✔
720
                    selectedProperties.AddExplicitlyImplemented(property);
24✔
721
                }
24✔
722
            }
114✔
723
        }
48✔
724
    }
128✔
725

726
#pragma warning disable AV1561
727
    private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
728
    {
128✔
729
        if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface)
128✔
730
        {
32✔
731
            var interfaces = typeToReflect.GetInterfaces();
32✔
732

733
            foreach (var interfaceType in interfaces)
160✔
734
            {
32✔
735
                foreach (var prop in interfaceType.GetProperties(flags))
176✔
736
                {
40✔
737
                    if (!prop.IsAbstract() || typeToReflect.IsInterface)
40✔
738
                    {
14✔
739
                        selectedProperties.AddFromInterface(prop);
14✔
740
                    }
14✔
741
                }
40✔
742
            }
32✔
743
        }
32✔
744
    }
128✔
745
#pragma warning restore AV1561
746

747
    private void LoadFields(Type typeToReflect, MemberKind kind)
748
    {
82✔
749
        var collectedFieldNames = new HashSet<string>();
82✔
750

751
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
752
        {
128✔
753
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
754
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
755

756
            var files = typeToReflect.GetFields(flags);
128✔
757

758
            foreach (var field in files)
1,172✔
759
            {
394✔
760
                if (HasVisibility(kind, field) && collectedFieldNames.Add(field.Name))
394✔
761
                {
48✔
762
                    selectedFields.Add(field);
48✔
763
                }
48✔
764
            }
394✔
765

766
            // Move to the base type
767
            typeToReflect = typeToReflect.BaseType;
128✔
768
        }
128✔
769
    }
82✔
770

771
    private static bool HasVisibility(MemberKind kind, FieldInfo field)
772
    {
394✔
773
        return (kind.HasFlag(MemberKind.Public) && field.IsPublic) ||
394✔
774
               (kind.HasFlag(MemberKind.Internal) && (field.IsAssembly || field.IsFamilyOrAssembly));
394✔
775
    }
394✔
776

777
    public MemberInfo[] Members { get; }
6✔
778

779
    public PropertyInfo[] Properties => selectedProperties.ToArray();
152✔
780

781
    public FieldInfo[] Fields => selectedFields.ToArray();
66✔
782

783
    private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
784
    {
785
        private readonly Dictionary<string, PropertyKind> kindMap = new();
82✔
786
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
82✔
787

788
        public IEnumerator<PropertyInfo> GetEnumerator()
789
        {
234✔
790
            return propertiesWithName.Select(x => x.Property).GetEnumerator();
676✔
791
        }
234✔
792

793
        IEnumerator IEnumerable.GetEnumerator()
UNCOV
794
        {
×
UNCOV
795
            return GetEnumerator();
×
UNCOV
796
        }
×
797

798
        private enum PropertyKind
799
        {
800
            Normal,
801
            ExplicitlyImplemented,
802
            Interface
803
        }
804

805
        public void AddExplicitlyImplemented(PropertyInfo property)
806
        {
24✔
807
            var name = property.Name.Split('.').Last();
24✔
808

809
            Add(name, property, PropertyKind.ExplicitlyImplemented);
24✔
810
        }
24✔
811

812
        public void AddNormal(PropertyInfo property)
813
        {
148✔
814
            Add(property.Name, property, PropertyKind.Normal);
148✔
815
        }
148✔
816

817
        public void AddFromInterface(PropertyInfo property)
818
        {
14✔
819
            Add(property.Name, property, PropertyKind.Interface);
14✔
820
        }
14✔
821

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