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

Sholtee / proxygen / 1078

15 Jun 2025 07:22AM UTC coverage: 92.859% (+0.2%) from 92.61%
1078

push

appveyor

Sholtee
Merge branch 'feature/#4' into v10.0.0-preview2

# Conflicts:
#	SRC/Private/Extensions/StreamExtensions.cs

4811 of 5181 relevant lines covered (92.86%)

0.93 hits per line

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

95.39
/SRC/Private/Extensions/Metadata/TypeExtensions.cs
1
/********************************************************************************
2
* TypeExtensions.cs                                                             *
3
*                                                                               *
4
* Author: Denes Solti                                                           *
5
********************************************************************************/
6
using System;
7
using System.Collections.Generic;
8
using System.Diagnostics;
9
using System.Linq;
10
using System.Linq.Expressions;
11
using System.Reflection;
12
using System.Text.RegularExpressions;
13

14
namespace Solti.Utils.Proxy.Internals
15
{
16
    using Properties;
17

18
    /// <summary>
19
    /// Helper methods for the <see cref="Type"/> class.
20
    /// </summary>
21
    internal static class TypeExtensions
22
    {
23
        //
24
        // https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names
25
        //
26
        // "&": by ref parameter
27
        // "*": pointer
28
        // "`d": generic type where "d" is an integer
29
        // "[T, TT]": generic parameter
30
        // "[<PropName_1>xXx, <PropName_2>xXx]": props belong to an anon object
31
        //
32

33
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
34
        private static readonly Regex FTypeNameReplacer = new(@"\&|\*|`\d+(\[[\w,<>]+\])?", RegexOptions.Compiled);
1✔
35

36
        /// <summary>
37
        /// Gets the friendly name of the given <see cref="Type"/>. Friendly name doesn't contain references for generic arguments, pointer features or the enclosing type.
38
        /// </summary>
39
        public static string GetFriendlyName(this Type src)
40
        {
1✔
41
            if (src.GetInnermostElementType()?.IsGenericType is true)
1✔
42
                src = src.GetGenericDefinition();
1✔
43

44
            return FTypeNameReplacer.Replace
1✔
45
            (
1✔
46
                src.IsNested()
1✔
47
                    ? src.Name 
1✔
48
                    : src.ToString(), 
1✔
49
                string.Empty
1✔
50
            );
1✔
51
        }
1✔
52

53
        /// <summary>
54
        /// Gets the generic definition of the given. Handles pointer types properly.
55
        /// </summary>
56
        public static Type GetGenericDefinition(this Type src)
57
        {
1✔
58
            return src switch
1✔
59
            {
1✔
60
                { IsArray: true } => GetGenericDefinitionCore(src).MakeArrayType(src.GetArrayRank()),
1✔
61
                { IsByRef: true } => GetGenericDefinitionCore(src).MakeByRefType(),
×
62
                { IsPointer: true } => GetGenericDefinitionCore(src).MakePointerType(),
×
63
                _ => src.GetGenericTypeDefinition()
1✔
64
            };
1✔
65

66
            static Type GetGenericDefinitionCore(Type src) => src.GetInnermostElementType()!.GetGenericDefinition();
1✔
67
        }
1✔
68

69
        /// <summary>
70
        /// Gets the qualified name of the given <see cref="Type"/>. Handles pointer types properly.
71
        /// </summary>
72
        public static string? GetQualifiedName(this Type src) 
73
        {
1✔
74
            src = src.GetInnermostElementType() ?? src;
1✔
75

76
            if (src.IsGenericType)
1✔
77
                src = src.GetGenericDefinition();
1✔
78

79
            return src.FullName;
1✔
80
        }
1✔
81

82
        /// <summary>
83
        /// Resolves the given pointer <see cref="Type"/> by returning the inner most element <see cref="Type"/>.
84
        /// </summary>
85
        public static Type? GetInnermostElementType(this Type src) 
86
        {
1✔
87
            Type? prev = null;
1✔
88

89
            for (Type? current = src; (current = current!.GetElementType()) is not null;)
1✔
90
                prev = current;
1✔
91

92
            return prev;
1✔
93
        }
1✔
94

95
        /// <summary>
96
        /// Gets the enclosing type if the given type. Handles generic parents properly.
97
        /// </summary>
98
        public static Type? GetEnclosingType(this Type src) 
99
        {
1✔
100
            src = src.GetInnermostElementType() ?? src;
1✔
101

102
            Type? enclosingType = src.DeclaringType;
1✔
103
            if (enclosingType is null)
1✔
104
                return null;
1✔
105

106
            if (src.IsGenericParameter)
1✔
107
                return enclosingType;
1✔
108

109
            //
110
            // "Cica<T>.Mica<TT>.Kutya" counts as generic, too: In open form it is returned as Cica<T>.Mica<TT>.Kutya<T, TT>
111
            // while in closed as "Cica<T>.Mica<TT>.Kutya<TConcrete1, TConcrete2>".
112
            //
113

114
            int gaCount = enclosingType.GetGenericArguments().Length;
1✔
115
            if (gaCount is 0)
1✔
116
                return enclosingType;
1✔
117

118
            Type[] gas = new Type[gaCount];
1✔
119
            Array.Copy(src.GetGenericArguments(), 0, gas, 0, gaCount);
1✔
120

121
            return enclosingType.MakeGenericType(gas);
1✔
122
        }
1✔
123

124
        /// <summary>
125
        /// Returns true if the given <see cref="Type"/> is nested.
126
        /// </summary>
127
        public static bool IsNested(this Type src) =>
128
            //
129
            // Types (for instance arrays) derived from embedded types are not embedded anymore.
130
            //
131

132
            (src.GetInnermostElementType() ?? src).IsNested;
1✔
133

134
        /// <summary>
135
        /// Returns true if the given <see cref="Type"/> represents a class and not a generic parameter.
136
        /// </summary>
137
        public static bool IsClass(this Type src) => !src.IsGenericParameter && src.IsClass;
1✔
138

139
        /// <summary>
140
        /// Returns true if the given <see cref="Type"/> is abstract.
141
        /// </summary>
142
        public static bool IsAbstract(this Type src) => src.IsAbstract && !src.IsSealed; // IL representation of static classes are "sealed abstract"
1✔
143

144
        /// <summary>
145
        /// Enumerates the methods defined on the given <see cref="Type"/>
146
        /// </summary>
147
        public static IEnumerable<MethodInfo> ListMethods(this Type src, bool includeStatic = false) => src
1✔
148
            .ListMembersInternal
1✔
149
            (
1✔
150
                static (t, f) => t.GetMethods(f),
1✔
151
                static m => m,
1✔
152
                includeStatic
1✔
153
            )
1✔
154
            .Where(static m => !m.IsSpecialName);
1✔
155

156
        /// <summary>
157
        /// Enumerates the properties defined on the given <see cref="Type"/>
158
        /// </summary>
159
        public static IEnumerable<PropertyInfo> ListProperties(this Type src, bool includeStatic = false) => src.ListMembersInternal
1✔
160
        (
1✔
161
            static (t, f) => t.GetProperties(f),
1✔
162
            static prop =>
1✔
163
            {
1✔
164
                if (prop.GetMethod is null)
1✔
165
                    return prop.SetMethod;
1✔
166

1✔
167
                if (prop.SetMethod is null)
1✔
168
                    return prop.GetMethod;
1✔
169

1✔
170
                //
1✔
171
                // Higher visibility has the precedence
1✔
172
                //
1✔
173

1✔
174
                return prop.GetMethod.GetAccessModifiers() > prop.SetMethod.GetAccessModifiers()
1✔
175
                    ? prop.GetMethod
1✔
176
                    : prop.SetMethod;
1✔
177
            },
1✔
178
            includeStatic
1✔
179
        );
1✔
180

181
        /// <summary>
182
        /// Enumerates the events defined on the given <see cref="Type"/>
183
        /// </summary>
184
        public static IEnumerable<EventInfo> ListEvents(this Type src, bool includeStatic = false) => src.ListMembersInternal
1✔
185
        (
1✔
186
            static (t, f) => t.GetEvents(f),
1✔
187
            
1✔
188
            //
1✔
189
            // Events always have Add & Remove method declared
1✔
190
            //
1✔
191

1✔
192
            static e => e.AddMethod,
1✔
193
            includeStatic
1✔
194
        );
1✔
195

196
        /// <summary>
197
        /// The core member enumerator. It searches the whole hierarchy and includes explicitly implemented interface members as well.
198
        /// </summary>
199
        private static IEnumerable<TMember> ListMembersInternal<TMember>
200
        (
201
            this Type src, 
202
            Func<Type, BindingFlags, TMember[]> getter, 
203
            Func<TMember, MethodInfo> getUnderlyingMethod,
204
            bool includeStatic
205
        ) where TMember: MemberInfo
206
        {
1✔
207
            if (src.IsGenericParameter)
1✔
208
                yield break;
1✔
209

210
            BindingFlags flags = 
1✔
211
                BindingFlags.Public |
1✔
212

1✔
213
                //
1✔
214
                // BindingFlags.FlattenHierarchy returns public and protected members only. Unfortunately explicit
1✔
215
                // implementations are private.
1✔
216
                // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=net-9.0#fields
1✔
217
                //
1✔
218

1✔
219
                //BindingFlags.FlattenHierarchy |
1✔
220

1✔
221
                BindingFlags.NonPublic |
1✔
222
                BindingFlags.Instance |
1✔
223
                BindingFlags.DeclaredOnly;
1✔
224

225
            //
226
            // As of NET6_0 we may declare static members on interfaces
227
            //
228

229
            if (includeStatic)
1✔
230
                flags |= BindingFlags.Static;
1✔
231

232
            if (src.IsInterface)
1✔
233
                foreach (TMember member in GetMembers())
1✔
234
                    yield return member;
1✔
235
            else
236
            {
1✔
237
                HashSet<MethodInfo> overriddenMethods = [];
1✔
238

239
                //
240
                // Order matters: we're processing the hierarchy towards the ancestor
241
                //
242

243
                foreach (TMember member in GetMembers())
1✔
244
                {
1✔
245
                    MethodInfo underlyingMethod = getUnderlyingMethod(member);
1✔
246

247
                    //
248
                    // When we encounter a virtual method, return only the last override
249
                    //
250

251
                    if (underlyingMethod.GetOverriddenMethod() is MethodInfo overriddenMethod && !overriddenMethods.Add(overriddenMethod))
1✔
252
                        continue;
×
253

254
                    if (overriddenMethods.Contains(underlyingMethod))
1✔
255
                        continue;
1✔
256

257
                    //
258
                    // We don't want to return private members
259
                    //
260

261
                    if (underlyingMethod.GetAccessModifiers() > AccessModifiers.Private)
1✔
262
                        yield return member;
1✔
263
                }
1✔
264
            }
1✔
265

266
            IEnumerable<TMember> GetMembers() => src.GetHierarchy().SelectMany(t => getter(t, flags));
1✔
267
        }
1✔
268

269
        /// <summary>
270
        /// Returns the constructors declared by user code on the given <see cref="Type"/>.
271
        /// </summary>
272
        public static IEnumerable<ConstructorInfo> GetDeclaredConstructors(this Type type)
273
        {
1✔
274
            //
275
            // Array type really sucks: All its constructors are generated by the compiler so from symbols
276
            // they cannot be retrieved.
277
            //
278

279
            if (type.IsArray)
1✔
280
                yield break;
1✔
281

282
            foreach (ConstructorInfo ctor in type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
1✔
283
                yield return ctor;
1✔
284
        }
1✔
285

286
        /// <summary>
287
        /// Enumerates all the base <see cref="Type"/>s.
288
        /// </summary>
289
        public static IEnumerable<Type> GetBaseTypes(this Type type) 
290
        {
1✔
291
            for (Type? baseType = type; (baseType = baseType!.GetBaseType()) is not null; )
1✔
292
                yield return baseType;
1✔
293
        }
1✔
294

295
        /// <summary>
296
        /// Returns all the interfaces, that were implemented or inherited by the current <see cref="Type"/>. Handles generic parameters properly.
297
        /// </summary>
298
        public static IEnumerable<Type> GetAllInterfaces(this Type type) => !type.IsGenericParameter
1✔
299
            ? type.GetInterfaces()
1✔
300
            : [];
1✔
301

302
        /// <summary>
303
        /// Returns the base of the given <see cref="Type"/>. Handles generic parameters properly.
304
        /// </summary>
305
        public static Type? GetBaseType(this Type src) => !src.IsGenericParameter
1✔
306
            ? src.BaseType
1✔
307
            : null;
1✔
308

309
        /// <summary>
310
        /// Returns the class or interface hierarchy starting from the current <see cref="Type"/>.
311
        /// </summary>
312
        public static IEnumerable<Type> GetHierarchy(this Type src)
313
        {
1✔
314
            yield return src;
1✔
315

316
            foreach (Type t in src.IsInterface ? src.GetAllInterfaces() : src.GetBaseTypes())
1✔
317
                yield return t;
1✔
318
        }
1✔
319

320
        /// <summary>
321
        /// Returns true if the given <see cref="Type"/> represents a delegate (for instance a <see cref="Func{TResult}"/> or <see cref="Action"/>).
322
        /// </summary>
323
        public static bool IsDelegate(this Type src) =>
324
            (src.GetInnermostElementType() ?? src).GetBaseTypes().Contains(typeof(Delegate)) && src != typeof(MulticastDelegate);
1✔
325

326
        /// <summary>
327
        /// Returns the generic arguments that are explicitly declared on the given <see cref="Type"/>. For instance
328
        /// <code>
329
        /// class Parent&lt;T&gt;
330
        /// {
331
        ///     class Child&lt;TT&gt; {}
332
        /// }
333
        /// typeof(Parent&lt;int&gt;.Child&lt;string&gt;).GetOwnGenericArguments() // [typeof(string)]
334
        /// </code>
335
        /// </summary>
336
        public static IEnumerable<Type> GetOwnGenericArguments(this Type src)
337
        {
1✔
338
            if (!src.IsGenericType)
1✔
339
                yield break;
1✔
340

341
            //
342
            // "Cica<T>.Mica<TT>.Kutya" counts as generic, too: In open form it is returned as Cica<T>.Mica<TT>.Kutya<T, TT>
343
            // while in closed as "Cica<T>.Mica<TT>.Kutya<TConcrete1, TConcrete2>".
344
            //
345

346
            Type[] 
1✔
347
                closedArgs = src.GetGenericArguments(),
1✔
348
                openArgs = (src = src.GetGenericTypeDefinition()).GetGenericArguments();
1✔
349

350
            for(int i = 0; i < openArgs.Length; i++)
1✔
351
            {
1✔
352
                Type openArg = openArgs[i];
1✔
353

354
                bool own = true;
1✔
355
                for (Type? parent = src; (parent = parent!.DeclaringType) is not null;)
1✔
356
                    //
357
                    // GetGenericArguments() will return empty array if "parent" is not generic
358
                    //
359

360
                    if (parent.GetGenericArguments().Any(arg => openArg.IsGenericParameter ? arg.IsGenericParameter && arg.Name == openArg.Name : arg == openArg))
1✔
361
                    {
1✔
362
                        own = false;
1✔
363
                        break;
1✔
364
                    }
365

366
                if (own) 
1✔
367
                    yield return closedArgs[i];
1✔
368
            } 
1✔
369
        }
1✔
370

371
        /// <summary>
372
        /// Associates <see cref="AccessModifiers"/> to the given <see cref="Type"/>.
373
        /// </summary>
374
        /// <remarks>Since this method may use reflection to determine the result, callers better cache the returned data</remarks>
375
        public static AccessModifiers GetAccessModifiers(this Type src)
376
        {
1✔
377
            src = src.GetInnermostElementType() ?? src;
1✔
378

379
            AccessModifiers am = src switch
1✔
380
            {
1✔
381
                { IsPublic: true, IsVisible: true } or { IsNestedPublic: true } => AccessModifiers.Public,
1✔
382
                { IsNestedFamily: true } => AccessModifiers.Protected,
1✔
383
                { IsNestedFamORAssem: true } => AccessModifiers.Protected | AccessModifiers.Internal,
1✔
384
                { IsNestedFamANDAssem: true }  => AccessModifiers.Protected | AccessModifiers.Private,
1✔
385
                { IsNestedAssembly: true } or { IsVisible: false, IsNested: false } => AccessModifiers.Internal,
1✔
386
                { IsNestedPrivate: true } => AccessModifiers.Private,
1✔
387
                _ => throw new InvalidOperationException(Resources.UNDETERMINED_ACCESS_MODIFIER)
×
388
            };
1✔
389

390
            if (src.IsGenericParameter)
1✔
391
                return am;
1✔
392

393
            //
394
            // Generic arguments may impact the visibility.
395
            //
396

397
            if (src.IsConstructedGenericType)
1✔
398
                foreach (Type ga in src.GetGenericArguments())
1✔
399
                    UpdateAm(ref am, ga);
1✔
400

401
            Type? enclosingType = src.GetEnclosingType();
1✔
402
            if (enclosingType is not null)
1✔
403
                UpdateAm(ref am, enclosingType);
1✔
404

405
            return am;
1✔
406

407
            static void UpdateAm(ref AccessModifiers am, Type t)
408
            {
1✔
409
                AccessModifiers @new = t.GetAccessModifiers();
1✔
410
                if (@new < am)
1✔
411
                    am = @new;
1✔
412
            }
1✔
413
        }
1✔
414

415
        /// <summary>
416
        /// Associates <see cref="RefType"/> to the given <see cref="Type"/>.
417
        /// </summary>
418
        /// <remarks>Since this method may inspect attributes to determine the result, callers better cache the returned data.</remarks>
419
        public static RefType GetRefType(this Type src) => src switch
1✔
420
        {
1✔
421
#if NETSTANDARD2_1_OR_GREATER
1✔
422
            { IsByRefLike: true }
1✔
423
#else
1✔
424
            _ when src
1✔
425
                    .GetCustomAttributes()
1✔
426
                    .Select(static ca => ca.GetType().FullName)
1✔
427
                    .Contains("System.Runtime.CompilerServices.IsByRefLikeAttribute", StringComparer.OrdinalIgnoreCase)
1✔
428
#endif
1✔
429
                => RefType.Ref, // ref struct
1✔
430
            { IsPointer: true } => RefType.Pointer,
1✔
431
            { IsArray: true } => RefType.Array,
1✔
432
            _ => RefType.None
1✔
433
        };
1✔
434

435
        //
436
        // IsFunctionPointer is available in net8.0+ only
437
        //
438

439
        private static Func<Type, bool> GetIsFunctionPointerCore()
440
        {
1✔
441
            PropertyInfo? prop = typeof(Type).GetProperty("IsFunctionPointer");
1✔
442
            ParameterExpression type = Expression.Parameter(typeof(Type), nameof(type));
1✔
443
            return Expression.Lambda<Func<Type, bool>>
×
444
            (
×
445
                body: prop is null ? Expression.Constant(false) : Expression.Property(type, prop),
×
446
                type
×
447
            ).Compile();
×
448
        }
1✔
449

450
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
451
        private static readonly Func<Type, bool> FIsFunctionPointerCore = GetIsFunctionPointerCore();
1✔
452

453
        /// <summary>
454
        /// Returns true if the given <see cref="Type"/> is a function pointer.
455
        /// </summary>
456
        public static bool IsFunctionPointer(this Type src) => FIsFunctionPointerCore(src);
1✔
457

458
        /// <summary>
459
        /// Returns the generic constraints associated with the given generic parameter.
460
        /// </summary>
461
        /// <param name="src">The generic parameter</param>
462
        /// <param name="declaringMember">Member on which the generic parameter is declared.</param>
463
        public static IEnumerable<Type> GetGenericConstraints(this Type src, MemberInfo declaringMember)
464
        {
1✔
465
            //
466
            // We can't query the declaring type using the src as the DeclaringType property never
467
            // returns specialized generic.
468
            //
469
            // Type declaringType = src.DeclaringMethod?.DeclaringType ?? src.DeclaringType;
470
            //
471

472
            Type declaringType = declaringMember switch
1✔
473
            {
1✔
474
                MethodInfo method => method.DeclaringType,
1✔
475
                Type type => type.DeclaringType,
×
476
                _ => throw new InvalidOperationException()
×
477
            };
1✔
478

479
            foreach (Type gpc in src.GetGenericParameterConstraints())
1✔
480
            {      
1✔
481
                //
482
                // We don't want a
483
                //     "where TT : struct, global::System.ValueType"
484
                //
485

486
                if (gpc == typeof(ValueType) && src.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint))
1✔
487
                    continue;
1✔
488

489
                if (declaringType.IsConstructedGenericType is true)
1✔
490
                {
1✔
491
                    //
492
                    // Get the specialized constraint from the declaring member
493
                    // (note that gpc.DeclaringXxX always returns the generic definition)
494
                    //
495

496
                    int position = declaringType.GetGenericTypeDefinition().GetGenericArguments().IndexOf(gpc);
1✔
497
                    if (position >= 0)
1✔
498
                    {
1✔
499
                        yield return declaringType.GetGenericArguments()[position];
1✔
500
                        continue;
1✔
501
                    }
502
                }
1✔
503

504
                yield return gpc;
1✔
505
            }
1✔
506
        }
1✔
507

508
        /// <summary>
509
        /// Returns the index of the given generic parameter:
510
        /// <code>
511
        /// class Foo&lt;T, TT&gt; {}
512
        /// typeof(Foo&lt;T, TT&gt;).GetGenericArguments()[1].GetGenericParameterIndex() // == 1
513
        /// </code>
514
        /// </summary>
515
        public static int GetGenericParameterIndex(this Type src)
516
        {
1✔
517
            if (!src.IsGenericParameter)
1✔
518
                return 0;
×
519

520
            return src.DeclaringMethod is not null
1✔
521
                ? GetIndex(src.DeclaringMethod.GetGenericArguments(), src)
1✔
522
                : GetIndex(src.DeclaringType.GetGenericArguments(), src) * -1;
1✔
523

524
            static int GetIndex(IEnumerable<Type> gas, Type src)
525
            {
1✔
526
                int result = gas.Select(static t => t.Name).IndexOf(src.Name);
1✔
527
                Debug.Assert(result >= 0);
1✔
528

529
                return result + 1;
1✔
530
            }
1✔
531
        }
1✔
532

533
        /// <summary>
534
        /// Checks the given <see cref="Type"/>s for equality. Handles generic parameters properly.
535
        /// </summary>
536
        public static bool EqualsTo(this Type src, Type that)
537
        {
1✔
538
            if (!GetBasicProps(src).Equals(GetBasicProps(that)))
1✔
539
                return false;
1✔
540

541
            Type? srcElement = src.GetElementType();
1✔
542
            if (srcElement is not null)
1✔
543
            {
1✔
544
                Type? thatElement = that.GetElementType();
1✔
545
                return thatElement is not null && srcElement.EqualsTo(thatElement);
×
546
            }
547

548
            if (src.IsGenericType)
1✔
549
                return 
1✔
550
                    that.IsGenericType && 
1✔
551
                    src.GetGenericTypeDefinition().Equals(that.GetGenericTypeDefinition()) && 
1✔
552
                    src.GetGenericArguments().SequenceEqual(that.GetGenericArguments(), TypeComparer.Instance);
1✔
553

554
            if (src.IsGenericParameter)
1✔
555
                return that.IsGenericParameter && src.GetGenericParameterIndex() == that.GetGenericParameterIndex();
1✔
556
                
557
            return src == that;
1✔
558

559
            static object GetBasicProps(Type t) => new
1✔
560
            {
1✔
561
                t.IsPrimitive,
1✔
562
                t.IsPointer,
1✔
563
                t.IsEnum,
1✔
564
                t.IsValueType,
1✔
565
                t.IsArray,
1✔
566
                t.IsByRef
1✔
567
            };
1✔
568
        }
1✔
569
    }
570
}
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