< Summary

Information
Class: MyNet.Reflection.TypeHelper
Assembly: MyNet.Reflection
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/TypeHelper.cs
Tag: 323_28699572109
Line coverage
76%
Covered lines: 87
Uncovered lines: 27
Coverable lines: 114
Total lines: 363
Line coverage: 76.3%
Branch coverage
67%
Covered branches: 47
Total branches: 70
Branch coverage: 67.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
GetAssemblyByName(...)100%11100%
GetAssemblyName(...)75%4483.33%
GetAssemblyNameWithoutOverhead(...)100%22100%
GetTypeNameWithAssembly(...)100%11100%
GetTypeName(...)100%11100%
FormatType(...)100%11100%
ConvertTypeToVersionIndependentType(...)38.88%721845%
FormatInnerTypes(...)100%11100%
GetInnerTypes(...)83.33%292479.41%
GetTypeFrom(...)66.66%291868%
GetTypeNameWithoutNamespace(...)100%22100%
GetTypeNamespace(...)50%22100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/TypeHelper.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="TypeHelper.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Diagnostics.CodeAnalysis;
 10using System.Globalization;
 11using System.Linq;
 12using System.Reflection;
 13
 14namespace MyNet.Reflection;
 15
 16/// <summary>
 17/// Helper class to work with types, for example, to get the type name without version information or to get the inner t
 18/// </summary>
 19public static class TypeHelper
 20{
 21    private const char InnerTypeCountStart = '`';
 22    private const char InternalTypeStart = '+';
 23    private const char InternalTypeEnd = '[';
 24    private const string AllTypesStart = "[[";
 25    private const char SingleTypeStart = '[';
 26    private const char SingleTypeEnd = ']';
 327    private static readonly char[] InnerTypeCountEnd = ['[', '+'];
 28
 29    /// <summary>
 30    /// A list of microsoft public key tokens.
 31    /// </summary>
 332    private static readonly HashSet<string> MicrosoftPublicKeyTokens =
 333    [
 334        "b77a5c561934e089",
 335        "b03f5f7f11d50a3a",
 336        "31bf3856ad364e35"
 337    ];
 38
 39    /// <summary>
 40    /// Gets the assembly by its name.
 41    /// </summary>
 42    /// <param name="name">The name of the assembly.</param>
 43    /// <returns>The assembly if found; otherwise, <c>null</c>.</returns>
 644    public static Assembly? GetAssemblyByName(string name) => AppDomain.CurrentDomain.GetAssemblies().SingleOrDefault(as
 45
 46    /// <summary>
 47    /// Gets the name of the assembly.
 48    /// </summary>
 49    /// <param name="fullTypeName">Full name of the type, for example <c>Catel.TypeHelper, Catel.Core</c>.</param>
 50    /// <returns>The assembly name retrieved from the type, for example <c>Catel.Core</c> or <c>null</c> if the assembly
 51    /// <exception cref="ArgumentException">The <paramref name="fullTypeName"/> is <c>null</c> or whitespace.</exception
 52    public static string GetAssemblyName(string fullTypeName)
 53    {
 2754        var lastGenericIndex = fullTypeName.LastIndexOf("]]", StringComparison.Ordinal);
 2755        if (lastGenericIndex != -1)
 56        {
 057            fullTypeName = fullTypeName[(lastGenericIndex + 2)..];
 58        }
 59
 2760        var splitterPos = fullTypeName.IndexOf(", ", StringComparison.Ordinal);
 2761        var assemblyName = splitterPos != -1 ? fullTypeName[(splitterPos + 1)..].Trim() : string.Empty;
 2462        return assemblyName;
 63    }
 64
 65    /// <summary>
 66    /// Gets the assembly name without overhead (version, public keytoken, etc).
 67    /// </summary>
 68    /// <param name="fullyQualifiedAssemblyName">Name of the fully qualified assembly.</param>
 69    /// <returns>The assembly without the overhead.</returns>
 70    /// <exception cref="ArgumentException">The <paramref name="fullyQualifiedAssemblyName"/> is <c>null</c> or whitespa
 71    public static string GetAssemblyNameWithoutOverhead(string fullyQualifiedAssemblyName)
 72    {
 973        var indexOfFirstComma = fullyQualifiedAssemblyName.IndexOf(',', StringComparison.OrdinalIgnoreCase);
 974        return indexOfFirstComma != -1 ? fullyQualifiedAssemblyName[..indexOfFirstComma] : fullyQualifiedAssemblyName;
 75    }
 76
 77    /// <summary>
 78    /// Gets the type name with assembly, but without the fully qualified assembly name. For example, this method provid
 79    /// the string:
 80    /// <para />
 81    /// <c>Catel.TypeHelper, Catel.Core, Version=1.0.0.0, PublicKeyToken=123456789</c>.
 82    /// <para />
 83    /// and will return:
 84    /// <para />
 85    /// <c>Catel.TypeHelper, Catel.Core</c>.
 86    /// </summary>
 87    /// <param name="fullTypeName">Full name of the type.</param>
 88    /// <returns>The type name including the assembly.</returns>
 89    /// <exception cref="ArgumentException">The <paramref name="fullTypeName"/> is <c>null</c> or whitespace.</exception
 90    public static string GetTypeNameWithAssembly(string fullTypeName)
 91    {
 392        var assemblyNameWithoutOverhead = GetAssemblyName(fullTypeName);
 393        var assemblyName = GetAssemblyNameWithoutOverhead(assemblyNameWithoutOverhead);
 394        var typeName = GetTypeName(fullTypeName);
 95
 396        return FormatType(assemblyName, typeName);
 97    }
 98
 99    /// <summary>
 100    /// Gets the name of the type without the assembly but including the namespace.
 101    /// </summary>
 102    /// <param name="fullTypeName">Full name of the type, for example <c>Catel.TypeHelper, Catel.Core</c>.</param>
 103    /// <returns>The type name retrieved from the type, for example <c>Catel.TypeHelper</c>.</returns>
 104    /// <exception cref="ArgumentException">The <paramref name="fullTypeName"/> is <c>null</c> or whitespace.</exception
 15105    public static string GetTypeName(string fullTypeName) => ConvertTypeToVersionIndependentType(fullTypeName, true);
 106
 107    /// <summary>
 108    ///   Formats a type in the official type description like [typename], [assemblyname].
 109    /// </summary>
 110    /// <param name = "assembly">Assembly name to format.</param>
 111    /// <param name = "type">Type name to format.</param>
 112    /// <returns>Type name like [typename], [assemblyname].</returns>
 113    /// <exception cref="ArgumentException">The <paramref name="assembly"/> is <c>null</c> or whitespace.</exception>
 114    /// <exception cref="ArgumentException">The <paramref name="type"/> is <c>null</c> or whitespace.</exception>
 6115    public static string FormatType(string assembly, string type) => $"{type}, {assembly}";
 116
 117    /// <summary>
 118    /// Converts a string representation of a type to a version independent type by removing the assembly version inform
 119    /// </summary>
 120    /// <param name="type">Type to convert.</param>
 121    /// <param name="stripAssemblies">if set to <c>true</c>, the assembly names will be stripped as well.</param>
 122    /// <returns>String representing the type without version information.</returns>
 123    /// <exception cref="ArgumentException">The <paramref name="type" /> is <c>null</c> or whitespace.</exception>
 124    public static string ConvertTypeToVersionIndependentType(string type, bool stripAssemblies = false)
 125    {
 126        const string innerTypesEnd = ",";
 127
 18128        var newType = type;
 18129        var innerTypes = GetInnerTypes(newType);
 130
 18131        if (innerTypes.Length > 0)
 132        {
 133            // Remove inner types, but never strip assemblies because we need the real original type
 0134            newType = newType.Replace($"[{FormatInnerTypes(innerTypes)}]", string.Empty, StringComparison.OrdinalIgnoreC
 0135            for (var i = 0; i < innerTypes.Length; i++)
 136            {
 0137                innerTypes[i] = ConvertTypeToVersionIndependentType(innerTypes[i], stripAssemblies);
 138            }
 139        }
 140
 18141        var splitterPos = newType.IndexOf(", ", StringComparison.Ordinal);
 18142        var typeName = splitterPos != -1 ? newType[..splitterPos].Trim() : newType;
 18143        var assemblyName = GetAssemblyName(newType);
 144
 145        // Remove version info from assembly (if not signed by Microsoft)
 18146        if (!string.IsNullOrWhiteSpace(assemblyName) && !stripAssemblies)
 147        {
 0148            var isMicrosoftAssembly = MicrosoftPublicKeyTokens.Any(assemblyName.Contains);
 0149            if (!isMicrosoftAssembly)
 150            {
 0151                assemblyName = GetAssemblyNameWithoutOverhead(assemblyName);
 152            }
 153
 0154            newType = FormatType(assemblyName, typeName);
 155        }
 156        else
 157        {
 18158            newType = typeName;
 159        }
 160
 36161        if (innerTypes.Length == 0) return newType;
 0162        var innerTypesIndex = stripAssemblies ? newType.Length : newType.IndexOf(innerTypesEnd, StringComparison.Ordinal
 0163        if (innerTypesIndex >= 0)
 164        {
 0165            newType = newType.Insert(innerTypesIndex, $"[{FormatInnerTypes(innerTypes, stripAssemblies)}]");
 166        }
 167
 0168        return newType;
 169    }
 170
 171    /// <summary>
 172    /// Formats multiple inner types into one string.
 173    /// </summary>
 174    /// <param name="innerTypes">The inner types.</param>
 175    /// <param name="stripAssemblies">if set to <c>true</c>, the assembly names will be stripped as well.</param>
 176    /// <returns>string representing a combination of all inner types.</returns>
 177    public static string FormatInnerTypes(IEnumerable<string> innerTypes, bool stripAssemblies = false)
 6178        => string.Join(",", innerTypes.Select(x =>
 6179        {
 6180            var type = stripAssemblies ? ConvertTypeToVersionIndependentType(x, true) : x;
 6181            return $"[{type}]";
 6182        }));
 183
 184    /// <summary>
 185    /// Returns the inner type of a type, for example, a generic array type.
 186    /// </summary>
 187    /// <param name="type">Full type which might contain an inner type.</param>
 188    /// <returns>Array of inner types.</returns>
 189    /// <exception cref="ArgumentException">The <paramref name="type"/> is <c>null</c> or whitespace.</exception>
 190    [SuppressMessage("Roslynator", "RCS1075:Avoid empty catch clause that catches System.Exception", Justification = "Ig
 191    public static string[] GetInnerTypes(string type)
 192    {
 27193        var innerTypes = new List<string>();
 194
 195        try
 196        {
 27197            var countIndex = type.IndexOf(InnerTypeCountStart, StringComparison.OrdinalIgnoreCase);
 27198            if (countIndex == -1)
 199            {
 21200                return [.. innerTypes];
 201            }
 202
 203            // This is a generic, but does the type definition also contain the inner types?
 6204            if (!type.Contains(AllTypesStart, StringComparison.OrdinalIgnoreCase))
 205            {
 0206                return [.. innerTypes];
 207            }
 208
 209            // Get the number of inner types
 6210            var innerTypeCountEnd = -1;
 36211            foreach (var t in InnerTypeCountEnd)
 212            {
 12213                var index = type.IndexOf(t, StringComparison.OrdinalIgnoreCase);
 12214                if (index != -1 && (innerTypeCountEnd == -1 || index < innerTypeCountEnd))
 215                {
 216                    // This value is more likely to be the one
 6217                    innerTypeCountEnd = index;
 218                }
 219            }
 220
 6221            var innerTypeCount = int.Parse(type.AsSpan(countIndex + 1, innerTypeCountEnd - countIndex - 1), CultureInfo.
 222
 223            // Remove all info until the first inner type
 6224            if (!type.Contains(InternalTypeStart.ToString(), StringComparison.OrdinalIgnoreCase))
 225            {
 226                // Just remove the info
 6227                type = type[(innerTypeCountEnd + 1)..];
 228            }
 229            else
 230            {
 231                // Remove the index, but not the numbers
 0232                var internalTypeEnd = type.IndexOf(InternalTypeEnd, StringComparison.OrdinalIgnoreCase);
 0233                type = type[(internalTypeEnd + 1)..];
 234            }
 235
 236            // Get all the inner types
 30237            for (var i = 0; i < innerTypeCount; i++)
 238            {
 239                // Get the start & end of this inner type
 9240                var innerTypeStart = type.IndexOf(SingleTypeStart, StringComparison.OrdinalIgnoreCase);
 9241                var innerTypeEnd = innerTypeStart + 1;
 9242                var openings = 1;
 243
 244                // Loop until we find the end
 222245                while (openings > 0)
 246                {
 213247                    switch (type[innerTypeEnd])
 248                    {
 249                        case SingleTypeStart:
 0250                            openings++;
 0251                            break;
 252                        case SingleTypeEnd:
 9253                            openings--;
 254                            break;
 255                    }
 256
 257                    // Increase current pos if we still have openings left
 213258                    if (openings > 0)
 259                    {
 204260                        innerTypeEnd++;
 261                    }
 262                }
 263
 9264                innerTypes.Add(type.Substring(innerTypeStart + 1, innerTypeEnd - innerTypeStart - 1));
 9265                type = type[(innerTypeEnd + 1)..];
 266            }
 6267        }
 0268        catch (Exception)
 269        {
 270            // Ignore exception
 0271        }
 272
 6273        return [.. innerTypes];
 21274    }
 275
 276    /// <summary>
 277    /// Gets the <see cref="Type"/> from a string representation of the type. This method will try to find the type in a
 278    /// </summary>
 279    /// <param name="valueType">The string representation of the type.</param>
 280    /// <returns>The <see cref="Type"/> if found; otherwise, <c>null</c>.</returns>
 281    public static Type? GetTypeFrom(string valueType)
 282    {
 283        // 1 - GetType
 6284        var type = Type.GetType(valueType);
 9285        if (type != null) return type;
 286
 287        // 2 - Entry Assembly
 3288        var entryAssembly = Assembly.GetEntryAssembly()!;
 3289        type = entryAssembly.GetType(valueType);
 3290        if (type != null) return type;
 291
 292        // 3 - Other loaded assemblies
 3293        var assemblies = AppDomain.CurrentDomain.GetAssemblies().Except([entryAssembly]);
 294
 295        // 4 - To speed things up, we check first in the already loaded assemblies.
 3296        var list = assemblies.ToList();
 438297        foreach (var assembly in list)
 298        {
 216299            type = assembly.GetType(valueType);
 216300            if (type != null) break;
 301        }
 302
 3303        if (type != null) return type;
 304
 3305        var loadedAssemblies = list.ToList();
 306
 307        // 5 - Assemblies referenced but not loaded
 438308        foreach (var loadedAssembly in list)
 309        {
 3546310            foreach (var referencedAssemblyName in loadedAssembly.GetReferencedAssemblies())
 311            {
 1557312                var found = loadedAssemblies.TrueForAll(x => x.GetName() != referencedAssemblyName);
 313
 1557314                if (found) continue;
 315                try
 316                {
 0317                    var referencedAssembly = Assembly.Load(referencedAssemblyName);
 0318                    type = referencedAssembly.GetType(valueType);
 0319                    if (type != null)
 0320                        break;
 0321                    loadedAssemblies.Add(referencedAssembly);
 0322                }
 0323                catch
 324                {
 325                    // We will ignore this, because the Type might still be in one of the other Assemblies.
 0326                }
 327            }
 328        }
 329
 3330        return type;
 331    }
 332
 333    /// <summary>
 334    /// Gets the type name without the assembly namespace.
 335    /// </summary>
 336    /// <param name="fullTypeName">Full name of the type, for example <c>Catel.TypeHelper, Catel.Core</c>.</param>
 337    /// <returns>The type name retrieved from the type, for example <c>TypeHelper</c>.</returns>
 338    public static string GetTypeNameWithoutNamespace(string fullTypeName)
 339    {
 6340        fullTypeName = GetTypeName(fullTypeName);
 341
 6342        var splitterPos = fullTypeName.LastIndexOf('.');
 343
 6344        var typeName = splitterPos != -1 ? fullTypeName[(splitterPos + 1)..].Trim() : fullTypeName;
 3345        return typeName;
 346    }
 347
 348    /// <summary>
 349    /// Gets the type namespace.
 350    /// </summary>
 351    /// <param name="fullTypeName">Full name of the type, for example <c>Catel.TypeHelper, Catel.Core</c>.</param>
 352    /// <returns>The type namespace retrieved from the type, for example <c>Catel</c>.</returns>
 353    public static string GetTypeNamespace(string fullTypeName)
 354    {
 3355        fullTypeName = GetTypeName(fullTypeName);
 356
 3357        var splitterPos = fullTypeName.LastIndexOf('.');
 358
 3359        var typeName = splitterPos != -1 ? fullTypeName[..splitterPos].Trim() : fullTypeName;
 3360        return typeName;
 361    }
 362}
 363