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

dennisdoomen / reflectify / 13750240168

09 Mar 2025 04:12PM UTC coverage: 94.737% (-0.3%) from 95.0%
13750240168

Pull #64

github

web-flow
Merge fdd3952f3 into 0440dcd10
Pull Request #64: Normal properties of a base-class should have precedence over explicitly implemented properties on the derived class.

223 of 238 branches covered (93.7%)

Branch coverage included in aggregate %.

44 of 47 new or added lines in 1 file covered. (93.62%)

353 of 370 relevant lines covered (95.41%)

77.42 hits per line

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

94.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 <see langword="true" /> if the type is derived from an open-generic type, or <see langword="false" /> otherwise.
23
    /// </summary>
24
    public static bool IsDerivedFromOpenGeneric(this Type type, Type openGenericType)
25
    {
18✔
26
        // do not consider a type to be derived from itself
27
        if (type == openGenericType)
18✔
28
        {
6✔
29
            return false;
6✔
30
        }
31

32
        // check subject and its base types against definition
33
        for (Type baseType = type;
12✔
34
             baseType is not null;
30✔
35
             baseType = baseType.BaseType)
18✔
36
        {
24✔
37
            if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == openGenericType)
24✔
38
            {
6✔
39
                return true;
6✔
40
            }
41
        }
18✔
42

43
        return false;
6✔
44
    }
18✔
45

46
    /// <summary>
47
    /// Returns the interfaces that the <paramref name="type"/> implements or inherits from that are concrete
48
    /// versions of the <paramref name="openGenericType"/>.
49
    /// </summary>
50
    public static Type[] GetClosedGenericInterfaces(this Type type, Type openGenericType)
51
    {
24✔
52
        if (type.IsGenericType && type.GetGenericTypeDefinition() == openGenericType)
24!
53
        {
×
54
            return [type];
×
55
        }
56

57
        Type[] interfaces = type.GetInterfaces();
24✔
58

59
        return interfaces
24✔
60
            .Where(t => t.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == openGenericType))
30✔
61
            .ToArray();
24✔
62
    }
24✔
63

64
    /// <summary>
65
    /// Returns <see langword="true" /> if the type is decorated with the specific <typeparamref name="TAttribute"/>,
66
    /// or <see langword="false" /> otherwise.
67
    /// </summary>
68
    public static bool HasAttribute<TAttribute>(this Type type)
69
        where TAttribute : Attribute
70
    {
42✔
71
        return type.IsDefined(typeof(TAttribute), inherit: false);
42✔
72
    }
42✔
73

74
    /// <summary>
75
    /// Returns <see langword="true" /> if the type is decorated with the specific <typeparamref name="TAttribute"/> <i>and</i>
76
    /// that attribute instance matches the predicate, or <see langword="false" /> otherwise.
77
    /// </summary>
78
    public static bool HasAttribute<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
79
        where TAttribute : Attribute
80
    {
30✔
81
        if (predicate is null)
30✔
82
        {
6✔
83
            throw new ArgumentNullException(nameof(predicate));
6✔
84
        }
85

86
        return type.GetCustomAttributes<TAttribute>(inherit: false).Any(predicate);
24✔
87
    }
24✔
88

89
    /// <summary>
90
    /// Returns <see langword="true" /> if the type or one its parents are decorated with the
91
    /// specific <typeparamref name="TAttribute"/>.
92
    /// </summary>
93
    public static bool HasAttributeInHierarchy<TAttribute>(this Type type)
94
        where TAttribute : Attribute
95
    {
18✔
96
        return type.IsDefined(typeof(TAttribute), inherit: true);
18✔
97
    }
18✔
98

99
    /// <summary>
100
    /// Returns <see langword="true" /> if the type or one its parents are decorated with the
101
    /// specific <typeparamref name="TAttribute"/> <i>and</i> that attribute instance
102
    /// matches the predicate. Returns <see langword="false" /> otherwise.
103
    /// </summary>
104
    public static bool HasAttributeInHierarchy<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
105
        where TAttribute : Attribute
106
    {
18✔
107
        if (predicate is null)
18✔
108
        {
6✔
109
            throw new ArgumentNullException(nameof(predicate));
6✔
110
        }
111

112
        return type.GetCustomAttributes<TAttribute>(inherit: true).Any(predicate);
12✔
113
    }
12✔
114

115
    /// <summary>
116
    /// Retrieves all custom attributes of the specified type from a class or its inheritance hierarchy.
117
    /// </summary>
118
    public static TAttribute[] GetMatchingAttributes<TAttribute>(this Type type)
119
        where TAttribute : Attribute
120
    {
12✔
121
        return (TAttribute[])type.GetCustomAttributes<TAttribute>(inherit: true);
12✔
122
    }
12✔
123

124
    /// <summary>
125
    /// Retrieves an array of attributes of a specified type that match the provided predicate.
126
    /// </summary>
127
    public static TAttribute[] GetMatchingAttributes<TAttribute>(this Type type, Func<TAttribute, bool> predicate)
128
        where TAttribute : Attribute
129
    {
24✔
130
        if (predicate is null)
24!
131
        {
×
132
            throw new ArgumentNullException(nameof(predicate));
×
133
        }
134

135
        return type.GetCustomAttributes<TAttribute>(inherit: true).Where(predicate).ToArray();
24✔
136
    }
24✔
137

138
    /// <summary>
139
    /// Returns <see langword="true" /> if the type overrides the Equals method, or <see langword="false" /> otherwise.
140
    /// </summary>
141
    public static bool OverridesEquals(this Type type)
142
    {
12✔
143
        MethodInfo method = type
12✔
144
            .GetMethod("Equals", [typeof(object)]);
12✔
145

146
        return method is not null
12!
147
               && method.GetBaseDefinition().DeclaringType != method.DeclaringType;
12✔
148
    }
12✔
149

150
    /// <summary>
151
    /// Determines whether the actual type is the same as, or inherits from, the expected type.
152
    /// </summary>
153
    /// <remarks>
154
    /// The expected type can also be an open generic type definition.
155
    /// </remarks>
156
    /// <returns><see langword="true" /> if the actual type is the same as, or inherits from, the expected type; otherwise, <see langword="false" />.</returns>
157
    public static bool IsSameOrInherits(this Type actualType, Type expectedType)
158
    {
36✔
159
        return actualType == expectedType ||
36✔
160
               expectedType.IsAssignableFrom(actualType) ||
36✔
161
               (actualType.BaseType is { IsGenericType: true } && actualType.BaseType.GetGenericTypeDefinition() == expectedType);
36✔
162
    }
36✔
163

164
    /// <summary>
165
    /// Returns <see langword="true" /> if the type is a compiler-generated type, or <see langword="false" /> otherwise.
166
    /// </summary>
167
    /// <remarks>
168
    /// Typical examples of compiler-generated types are anonymous types, tuples, and records.
169
    /// </remarks>
170
    public static bool IsCompilerGenerated(this Type type)
171
    {
24✔
172
        return type.IsRecord() ||
24✔
173
               type.IsAnonymous() ||
24✔
174
               type.IsTuple();
24✔
175
    }
24✔
176

177
    /// <summary>
178
    /// Returns <see langword="true" /> if the type has a readable name, or <see langword="false" />
179
    /// if it is a compiler-generated name.
180
    /// </summary>
181
    public static bool HasFriendlyName(this Type type)
182
    {
18✔
183
        return !type.IsAnonymous() && !type.IsTuple();
18✔
184
    }
18✔
185

186
    /// <summary>
187
    /// Return <see langword="true" /> if the type is a tuple type; otherwise, <see langword="false" />
188
    /// </summary>
189
    public static bool IsTuple(this Type type)
190
    {
42✔
191
        if (!type.IsGenericType)
42✔
192
        {
18✔
193
            return false;
18✔
194
        }
195

196
#if NETCOREAPP2_0_OR_GREATER || NET471_OR_GREATER || NETSTANDARD2_1_OR_GREATER
197
        return typeof(ITuple).IsAssignableFrom(type);
16✔
198
#else
199
        Type openType = type.GetGenericTypeDefinition();
8✔
200

201
        return openType == typeof(ValueTuple<>)
8!
202
               || openType == typeof(ValueTuple<,>)
8✔
203
               || openType == typeof(ValueTuple<,,>)
8✔
204
               || openType == typeof(ValueTuple<,,,>)
8✔
205
               || openType == typeof(ValueTuple<,,,,>)
8✔
206
               || openType == typeof(ValueTuple<,,,,,>)
8✔
207
               || openType == typeof(ValueTuple<,,,,,,>)
8✔
208
               || (openType == typeof(ValueTuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]))
8✔
209
               || openType == typeof(Tuple<>)
8✔
210
               || openType == typeof(Tuple<,>)
8✔
211
               || openType == typeof(Tuple<,,>)
8✔
212
               || openType == typeof(Tuple<,,,>)
8✔
213
               || openType == typeof(Tuple<,,,,>)
8✔
214
               || openType == typeof(Tuple<,,,,,>)
8✔
215
               || openType == typeof(Tuple<,,,,,,>)
8✔
216
               || (openType == typeof(Tuple<,,,,,,,>) && IsTuple(type.GetGenericArguments()[7]));
8✔
217
#endif
218
    }
42✔
219

220
    /// <summary>
221
    /// Returns <see langword="true" /> if the type is an anonymous type, or <see langword="false" /> otherwise.
222
    /// </summary>
223
    public static bool IsAnonymous(this Type type)
224
    {
54✔
225
        if (!type.FullName!.Contains("AnonymousType"))
54✔
226
        {
36✔
227
            return false;
36✔
228
        }
229

230
        return type.HasAttribute<CompilerGeneratedAttribute>();
18✔
231
    }
54✔
232

233
    /// <summary>
234
    /// Return <see langword="true" /> if the type is a struct or class record type; otherwise, <see langword="false" />.
235
    /// </summary>
236
    public static bool IsRecord(this Type type)
237
    {
42✔
238
        return type.IsRecordClass() || type.IsRecordStruct();
42✔
239
    }
42✔
240

241
    /// <summary>
242
    /// Returns <see langword="true" /> if the type is a class record type; otherwise, <see langword="false" />.
243
    /// </summary>
244
    public static bool IsRecordClass(this Type type)
245
    {
60✔
246
        return type.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) is { } &&
60!
247
               type.GetProperty("EqualityContract", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)?
60✔
248
                   .GetMethod?.HasAttribute<CompilerGeneratedAttribute>() == true;
60✔
249
    }
60✔
250

251
    /// <summary>
252
    /// Return <see langword="true" /> if the type is a record struct; otherwise, <see langword="false" />
253
    /// </summary>
254
    public static bool IsRecordStruct(this Type type)
255
    {
54✔
256
        // As noted here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/record-structs#open-questions
257
        // recognizing record structs from metadata is an open point. The following check is based on common sense
258
        // and heuristic testing, apparently giving good results but not supported by official documentation.
259
        return type.BaseType == typeof(ValueType) &&
54!
260
               type.GetMethod("PrintMembers", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, null,
54✔
261
                   [typeof(StringBuilder)], null) is { } &&
54✔
262
               type.GetMethod("op_Equality", BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly, null,
54✔
263
                       [type, type], null)?
54✔
264
                   .HasAttribute<CompilerGeneratedAttribute>() == true;
54✔
265
    }
54✔
266

267
    /// <summary>
268
    /// Determines whether the specified type is a KeyValuePair.
269
    /// </summary>
270
    /// <param name="type">The type to check.</param>
271
    /// <returns><see langword="true" /> if the type is a KeyValuePair; otherwise, <see langword="false" />.</returns>
272
    public static bool IsKeyValuePair(this Type type)
273
    {
42✔
274
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
42✔
275
    }
42✔
276
}
277

278
internal static class TypeMemberExtensions
279
{
280
    private static readonly ConcurrentDictionary<(Type Type, MemberKind Kind), Reflector> ReflectorCache = new();
6✔
281

282
    /// <summary>
283
    /// Gets the public, internal, explicitly implemented and/or default properties of a type hierarchy.
284
    /// </summary>
285
    /// <param name="type">The type to reflect.</param>
286
    /// <param name="kind">The kind of properties to include in the response.</param>
287
    public static PropertyInfo[] GetProperties(this Type type, MemberKind kind)
288
    {
152✔
289
        return GetFor(type, kind).Properties;
152✔
290
    }
152✔
291

292
    /// <summary>
293
    /// Finds the property by a case-sensitive name and with a certain visibility.
294
    /// </summary>
295
    /// <remarks>
296
    /// Normal property get priority over explicitly implemented properties and default interface properties.
297
    /// </remarks>
298
    /// <returns>
299
    /// Returns <see langword="null" /> if no such property exists.
300
    /// </returns>
301
    public static PropertyInfo FindProperty(this Type type, string propertyName, MemberKind memberVisibility)
302
    {
94✔
303
        if (propertyName is null or "")
94✔
304
        {
12✔
305
            throw new ArgumentException("The property name cannot be null or empty", nameof(propertyName));
12✔
306
        }
307

308
        var properties = type.GetProperties(memberVisibility);
82✔
309

310
        return Array.Find(properties, p =>
82✔
311
            p.Name == propertyName || p.Name.EndsWith("." + propertyName, StringComparison.Ordinal));
236✔
312
    }
82✔
313

314
    /// <summary>
315
    /// Gets the public and/or internal fields of a type hierarchy.
316
    /// </summary>
317
    /// <param name="type">The type to reflect.</param>
318
    /// <param name="kind">The kind of fields to include in the response.</param>
319
    public static FieldInfo[] GetFields(this Type type, MemberKind kind)
320
    {
66✔
321
        return GetFor(type, kind).Fields;
66✔
322
    }
66✔
323

324
    /// <summary>
325
    /// Finds the field by a case-sensitive name.
326
    /// </summary>
327
    /// <returns>
328
    /// Returns <see langword="null" /> if no such field exists.
329
    /// </returns>
330
    public static FieldInfo FindField(this Type type, string fieldName, MemberKind memberVisibility)
331
    {
54✔
332
        if (fieldName is null or "")
54✔
333
        {
12✔
334
            throw new ArgumentException("The field name cannot be null or empty", nameof(fieldName));
12✔
335
        }
336

337
        var fields = type.GetFields(memberVisibility);
42✔
338

339
        return Array.Find(fields, p => p.Name == fieldName);
84✔
340
    }
42✔
341

342
    /// <summary>
343
    /// Gets a combination of <see cref="GetProperties"/> and <see cref="GetFields"/>.
344
    /// </summary>
345
    /// <param name="type">The type to reflect.</param>
346
    /// <param name="kind">The kind of fields and properties to include in the response.</param>
347
    public static MemberInfo[] GetMembers(this Type type, MemberKind kind)
348
    {
6✔
349
        return GetFor(type, kind).Members;
6✔
350
    }
6✔
351

352
    private static Reflector GetFor(Type typeToReflect, MemberKind kind)
353
    {
224✔
354
        return ReflectorCache.GetOrAdd((typeToReflect, kind),
224✔
355
            static key => new Reflector(key.Type, key.Kind));
306✔
356
    }
224✔
357

358
    /// <summary>
359
    /// Finds a method by its name, parameter types and visibility. Returns <see langword="null" /> if no such method exists.
360
    /// </summary>
361
    /// <remarks>
362
    /// If you don't specify any parameter types, the method will be found by its name only.
363
    /// </remarks>
364
#pragma warning disable AV1561
365
    public static MethodInfo FindMethod(this Type type, string methodName, MemberKind kind, params Type[] parameterTypes)
366
#pragma warning restore AV1561
367
    {
84✔
368
        if (methodName is null or "")
84✔
369
        {
12✔
370
            throw new ArgumentException("The method name cannot be null or empty", nameof(methodName));
12✔
371
        }
372

373
        var flags = kind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
72✔
374

375
        return type
72✔
376
            .GetMethods(flags)
72✔
377
            .SingleOrDefault(m => m.Name == methodName && HasSameParameters(parameterTypes, m));
540✔
378
    }
72✔
379

380
    /// <summary>
381
    /// Finds a parameterless method by its name and visibility. Returns <see langword="null" /> if no such method exists.
382
    /// </summary>
383
    public static MethodInfo FindParameterlessMethod(this Type type, string methodName, MemberKind memberKind)
384
    {
6✔
385
        return type.FindMethod(methodName, memberKind);
6✔
386
    }
6✔
387

388
    private static bool HasSameParameters(Type[] parameterTypes, MethodInfo method)
389
    {
60✔
390
        if (parameterTypes.Length == 0)
60✔
391
        {
36✔
392
            // If we don't specify any specific parameters, it matches always.
393
            return true;
36✔
394
        }
395

396
        return method.GetParameters()
24✔
397
            .Select(p => p.ParameterType)
66✔
398
            .SequenceEqual(parameterTypes);
24✔
399
    }
60✔
400

401
    /// <summary>
402
    /// Determines whether the type has a method with the specified name and visibility.
403
    /// </summary>
404
#pragma warning disable AV1561
405
    public static bool HasMethod(this Type type, string methodName, MemberKind memberKind, params Type[] parameterTypes)
406
#pragma warning restore AV1561
407
    {
6✔
408
        return type.FindMethod(methodName, memberKind, parameterTypes) is not null;
6✔
409
    }
6✔
410

411
    public static PropertyInfo FindIndexer(this Type type, MemberKind memberKind, params Type[] parameterTypes)
412
    {
18✔
413
        var flags = memberKind.ToBindingFlags() | BindingFlags.Instance | BindingFlags.Static;
18✔
414

415
        return type.GetProperties(flags)
18✔
416
            .SingleOrDefault(p =>
18✔
417
                p.IsIndexer() && p.GetIndexParameters().Select(i => i.ParameterType).SequenceEqual(parameterTypes));
72✔
418
    }
18✔
419

420
#pragma warning disable AV1561
421

422
    /// <summary>
423
    /// Finds an explicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
424
    /// Returns <see langword="null" /> if no such operator exists.
425
    /// </summary>
426
    public static MethodInfo FindExplicitConversionOperator(this Type type, Type sourceType, Type targetType)
427
    {
12✔
428
        return type
12✔
429
            .GetConversionOperators(sourceType, targetType, name => name is "op_Explicit")
6✔
430
            .SingleOrDefault();
12✔
431
    }
12✔
432

433
    /// <summary>
434
    /// Finds an implicit conversion operator from the <paramref name="sourceType"/> to the <paramref name="targetType"/>.
435
    /// Returns <see langword="null" /> if no such operator exists.
436
    /// </summary>
437
    public static MethodInfo FindImplicitConversionOperator(this Type type, Type sourceType, Type targetType)
438
    {
12✔
439
        return type
12✔
440
            .GetConversionOperators(sourceType, targetType, name => name is "op_Implicit")
6✔
441
            .SingleOrDefault();
12✔
442
    }
12✔
443

444
    private static IEnumerable<MethodInfo> GetConversionOperators(this Type type, Type sourceType, Type targetType,
445
#pragma warning restore AV1561
446
        Func<string, bool> predicate)
447
    {
24✔
448
        return type
24✔
449
            .GetMethods(BindingFlags.Static | BindingFlags.Public)
24✔
450
            .Where(m =>
24✔
451
                m.IsPublic
72✔
452
                && m.IsStatic
72✔
453
                && m.IsSpecialName
72✔
454
                && m.ReturnType == targetType
72✔
455
                && predicate(m.Name)
72✔
456
                && m.GetParameters() is { Length: 1 } parameters
72✔
457
                && parameters[0].ParameterType == sourceType);
72✔
458
    }
24✔
459
}
460

461
internal static class TypeExtensions
462
{
463
    /// <summary>
464
    /// If the type provided is a nullable type, gets the underlying type. Returns the type itself otherwise.
465
    /// </summary>
466
    public static Type NullableOrActualType(this Type type)
467
    {
×
468
        if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
×
469
        {
×
470
            type = type.GetGenericArguments()[0];
×
471
        }
×
472

473
        return type;
×
474
    }
×
475
}
476

477
internal static class MemberInfoExtensions
478
{
479
    public static bool HasAttribute<TAttribute>(this MemberInfo member)
480
        where TAttribute : Attribute
481
    {
30✔
482
        // Do not use MemberInfo.IsDefined
483
        // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work.
484
        // https://github.com/dotnet/runtime/issues/30219
485
        return Attribute.IsDefined(member, typeof(TAttribute), inherit: false);
30✔
486
    }
30✔
487

488
    /// <summary>
489
    /// Determines whether the member has an attribute of the specified type that matches the predicate.
490
    /// </summary>
491
    public static bool HasAttribute<TAttribute>(this MemberInfo member, Func<TAttribute, bool> predicate)
492
        where TAttribute : Attribute
493
    {
18✔
494
        if (predicate is null)
18✔
495
        {
6✔
496
            throw new ArgumentNullException(nameof(predicate));
6✔
497
        }
498

499
        return member.GetCustomAttributes<TAttribute>().Any(a => predicate(a));
24✔
500
    }
12✔
501

502
    public static bool HasAttributeInHierarchy<TAttribute>(this MemberInfo member)
503
        where TAttribute : Attribute
504
    {
×
505
        // Do not use MemberInfo.IsDefined
506
        // There is an issue with PropertyInfo and EventInfo preventing the inherit option to work.
507
        // https://github.com/dotnet/runtime/issues/30219
508
        return Attribute.IsDefined(member, typeof(TAttribute), inherit: true);
×
509
    }
×
510
}
511

512
internal static class PropertyInfoExtensions
513
{
514
    /// <summary>
515
    /// Returns <see langword="true" /> if the property is an indexer, or <see langword="false" /> otherwise.
516
    /// </summary>
517
    public static bool IsIndexer(this PropertyInfo member)
518
    {
228✔
519
        return member.GetIndexParameters().Length != 0;
228✔
520
    }
228✔
521

522
    /// <summary>
523
    /// Returns <see langword="true" /> if the property is explicitly implemented on the
524
    /// <see cref="MemberInfo.DeclaringType"/>, or <see langword="false" /> otherwise.
525
    /// </summary>
526
    public static bool IsExplicitlyImplemented(this PropertyInfo prop)
527
    {
262✔
528
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
529
        return prop.Name.Contains('.');
176✔
530
#else
531
        return prop.Name.IndexOf('.') != -1;
86✔
532
#endif
533
    }
262✔
534

535
    /// <summary>
536
    /// Returns <see langword="true" /> if the property has a public getter or setter, or <see langword="false" /> otherwise.
537
    /// </summary>
538
    public static bool IsPublic(this PropertyInfo prop)
539
    {
196✔
540
        return prop.GetMethod is { IsPublic: true } || prop.SetMethod is { IsPublic: true };
196✔
541
    }
196✔
542

543
    /// <summary>
544
    /// Returns <see langword="true" /> if the property is internal, or <see langword="false" /> otherwise.
545
    /// </summary>
546
    /// <param name="prop">The property to check.</param>
547
    public static bool IsInternal(this PropertyInfo prop)
548
    {
60✔
549
        return prop.GetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true } ||
60!
550
               prop.SetMethod is { IsAssembly: true } or { IsFamilyOrAssembly: true };
60✔
551
    }
60✔
552

553
    /// <summary>
554
    /// Returns <see langword="true" /> if the property is abstract, or <see langword="false" /> otherwise.
555
    /// </summary>
556
    /// <param name="prop">The property to check.</param>
557
    public static bool IsAbstract(this PropertyInfo prop)
558
    {
40✔
559
        return prop.GetMethod is { IsAbstract: true } || prop.SetMethod is { IsAbstract: true };
40✔
560
    }
40✔
561
}
562

563
/// <summary>
564
/// Defines the kinds of members you want to get when querying for the fields and properties of a type.
565
/// </summary>
566
[Flags]
567
internal enum MemberKind
568
{
569
    None,
570
    Public = 1,
571
    Internal = 2,
572
    ExplicitlyImplemented = 4,
573
    DefaultInterfaceProperties = 8,
574
    Static = 16
575
}
576

577
internal static class MemberKindExtensions
578
{
579
    public static BindingFlags ToBindingFlags(this MemberKind kind)
580
    {
90✔
581
        BindingFlags flags = BindingFlags.Default;
90✔
582

583
        if (kind.HasFlag(MemberKind.Public))
90✔
584
        {
72✔
585
            flags |= BindingFlags.Public;
72✔
586
        }
72✔
587

588
        if (kind.HasFlag(MemberKind.Internal))
90✔
589
        {
18✔
590
            flags |= BindingFlags.NonPublic;
18✔
591
        }
18✔
592

593
        return flags;
90✔
594
    }
90✔
595
}
596

597
/// <summary>
598
/// Helper class to get all the public and internal fields and properties from a type.
599
/// </summary>
600
internal sealed class Reflector
601
{
602
    private readonly List<FieldInfo> selectedFields = new();
82✔
603
    private readonly OrderedPropertyCollection selectedProperties = new();
82✔
604

605
    public Reflector(Type typeToReflect, MemberKind kind)
82✔
606
    {
82✔
607
        LoadProperties(typeToReflect, kind);
82✔
608
        LoadFields(typeToReflect, kind);
82✔
609

610
        Members = [.. selectedProperties, .. selectedFields];
82✔
611
    }
82✔
612

613
    private void LoadProperties(Type typeToReflect, MemberKind kind)
614
    {
82✔
615
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
616
        {
128✔
617
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
618
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
619

620
            var allProperties = typeToReflect.GetProperties(flags);
128✔
621

622
            AddNormalProperties(kind, allProperties);
128✔
623

624
            AddExplicitlyImplementedProperties(kind, allProperties);
128✔
625

626
            AddInterfaceProperties(typeToReflect, kind, flags);
128✔
627

628
            // Move to the base type
629
            typeToReflect = typeToReflect.BaseType;
128✔
630
        }
128✔
631
    }
82✔
632

633
    private void AddNormalProperties(MemberKind kind, PropertyInfo[] allProperties)
634
    {
128✔
635
        if (kind.HasFlag(MemberKind.Public) || kind.HasFlag(MemberKind.Internal) ||
128✔
636
            kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
637
        {
114✔
638
            foreach (var property in allProperties)
902✔
639
            {
280✔
640
                if (HasVisibility(kind, property) && !property.IsExplicitlyImplemented())
280✔
641
                {
148✔
642
                    selectedProperties.AddNormal(property);
148✔
643
                }
148✔
644
            }
280✔
645
        }
114✔
646
    }
128✔
647

648
    private static bool HasVisibility(MemberKind kind, PropertyInfo prop)
649
    {
280✔
650
        return (kind.HasFlag(MemberKind.Public) && prop.IsPublic()) ||
280✔
651
               (kind.HasFlag(MemberKind.Internal) && prop.IsInternal());
280✔
652
    }
280✔
653

654
    private void AddExplicitlyImplementedProperties(MemberKind kind, PropertyInfo[] allProperties)
655
    {
128✔
656
        if (kind.HasFlag(MemberKind.ExplicitlyImplemented))
128✔
657
        {
48✔
658
            foreach (var property in allProperties)
372✔
659
            {
114✔
660
                if (property.IsExplicitlyImplemented())
114✔
661
                {
24✔
662
                    selectedProperties.AddExplicitlyImplemented(property);
24✔
663
                }
24✔
664
            }
114✔
665
        }
48✔
666
    }
128✔
667

668
#pragma warning disable AV1561
669
    private void AddInterfaceProperties(Type typeToReflect, MemberKind kind, BindingFlags flags)
670
    {
128✔
671
        if (kind.HasFlag(MemberKind.DefaultInterfaceProperties) || typeToReflect.IsInterface)
128✔
672
        {
32✔
673
            var interfaces = typeToReflect.GetInterfaces();
32✔
674

675
            foreach (var interfaceType in interfaces)
160✔
676
            {
32✔
677
                foreach (var prop in interfaceType.GetProperties(flags))
176✔
678
                {
40✔
679
                    if (!prop.IsAbstract() || typeToReflect.IsInterface)
40✔
680
                    {
14✔
681
                        selectedProperties.AddFromInterface(prop);
14✔
682
                    }
14✔
683
                }
40✔
684
            }
32✔
685
        }
32✔
686
    }
128✔
687
#pragma warning restore AV1561
688

689
    private void LoadFields(Type typeToReflect, MemberKind kind)
690
    {
82✔
691
        var collectedFieldNames = new HashSet<string>();
82✔
692

693
        while (typeToReflect != null && typeToReflect != typeof(object))
210✔
694
        {
128✔
695
            BindingFlags flags = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic;
128✔
696
            flags |= kind.HasFlag(MemberKind.Static) ? BindingFlags.Static : BindingFlags.Instance;
128✔
697

698
            var files = typeToReflect.GetFields(flags);
128✔
699

700
            foreach (var field in files)
1,172✔
701
            {
394✔
702
                if (HasVisibility(kind, field) && collectedFieldNames.Add(field.Name))
394✔
703
                {
48✔
704
                    selectedFields.Add(field);
48✔
705
                }
48✔
706
            }
394✔
707

708
            // Move to the base type
709
            typeToReflect = typeToReflect.BaseType;
128✔
710
        }
128✔
711
    }
82✔
712

713
    private static bool HasVisibility(MemberKind kind, FieldInfo field)
714
    {
394✔
715
        return (kind.HasFlag(MemberKind.Public) && field.IsPublic) ||
394✔
716
               (kind.HasFlag(MemberKind.Internal) && (field.IsAssembly || field.IsFamilyOrAssembly));
394✔
717
    }
394✔
718

719
    public MemberInfo[] Members { get; }
6✔
720

721
    public PropertyInfo[] Properties => selectedProperties.ToArray();
152✔
722

723
    public FieldInfo[] Fields => selectedFields.ToArray();
66✔
724

725
    private class OrderedPropertyCollection : IEnumerable<PropertyInfo>
726
    {
727
        private readonly Dictionary<string, PropertyKind> kindMap = new();
82✔
728
        private readonly List<(string Name, PropertyInfo Property)> propertiesWithName = new();
82✔
729

730
        public IEnumerator<PropertyInfo> GetEnumerator()
731
        {
234✔
732
            return propertiesWithName.Select(x => x.Property).GetEnumerator();
676✔
733
        }
234✔
734

735
        IEnumerator IEnumerable.GetEnumerator()
NEW
736
        {
×
NEW
737
            return GetEnumerator();
×
NEW
738
        }
×
739

740
        private enum PropertyKind
741
        {
742
            Normal,
743
            ExplicitlyImplemented,
744
            Interface
745
        }
746

747
        public void AddExplicitlyImplemented(PropertyInfo property)
748
        {
24✔
749
            var name = property.Name.Split('.').Last();
24✔
750

751
            Add(name, property, PropertyKind.ExplicitlyImplemented);
24✔
752
        }
24✔
753

754
        public void AddNormal(PropertyInfo property)
755
        {
148✔
756
            Add(property.Name, property, PropertyKind.Normal);
148✔
757
        }
148✔
758

759
        public void AddFromInterface(PropertyInfo property)
760
        {
14✔
761
            Add(property.Name, property, PropertyKind.Interface);
14✔
762
        }
14✔
763

764
        private void Add(string name, PropertyInfo property, PropertyKind kind)
765
        {
186✔
766
            if (property.IsIndexer())
186✔
767
            {
6✔
768
                // We explicitly skip indexers
769
            }
6✔
770
            else if (!kindMap.TryGetValue(name, out var existingKind))
180✔
771
            {
150✔
772
                kindMap[name] = kind;
150✔
773
                propertiesWithName.Add((name, property));
150✔
774
            }
150✔
775
            else if (existingKind == PropertyKind.ExplicitlyImplemented && kind == PropertyKind.Normal)
30✔
776
            {
6✔
777
                // Normal properties have priority over interface properties
778
                kindMap[name] = kind;
6✔
779
                propertiesWithName.RemoveAll(x => x.Name == name);
12✔
780
                propertiesWithName.Add((name, property));
6✔
781
            }
6✔
782
            else
783
            {
24✔
784
                // Property with that name already exists
785
            }
24✔
786
        }
186✔
787
    }
788
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc