< Summary - PropertyGridHelpers Code Coverage

Information
Class: PropertyGridHelpers.Support.Support
Assembly: PropertyGridHelpers
File(s): c:\agent\_work\9\s\Code\PropertyGridHelpers\Support\Support.cs
Tag: PropertyGridHelpers Build_2025.7.15.1_#485
Line coverage
100%
Covered lines: 159
Uncovered lines: 0
Coverable lines: 159
Total lines: 582
Line coverage: 100%
Branch coverage
100%
Covered branches: 72
Total branches: 72
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

c:\agent\_work\9\s\Code\PropertyGridHelpers\Support\Support.cs

#LineLine coverage
 1using PropertyGridHelpers.Attributes;
 2using PropertyGridHelpers.Enums;
 3using System;
 4using System.Collections;
 5using System.ComponentModel;
 6using System.Diagnostics;
 7using System.Globalization;
 8using System.Linq;
 9using System.Reflection;
 10using System.Resources;
 11using System.Threading;
 12using System.Windows.Forms;
 13
 14namespace PropertyGridHelpers.Support
 15{
 16    /// <summary>
 17    /// Functions used to Support the processes provided by the PropertyGridHelpers library.
 18    /// </summary>
 19    public static class Support
 20    {
 21        /// <summary>
 22        /// Gets the resources names from the assembly where the enum is located.
 23        /// </summary>
 24        /// <param name="enumType">Type of the enum.</param>
 25        /// <returns>
 26        /// Returns a string array containing the names of all resources in  the assembly where the passed in enum type
 27        /// is located.
 28        /// </returns>
 29        /// <exception cref="ArgumentNullException">Thrown when the parameter <paramref name="enumType"/> is null</excep
 30        /// <exception cref="ArgumentException">Throw when the parameter <paramref name="enumType"/> is not an Enum type
 31        /// <!-- IntelliSense Only -->
 32        /// See <see href="https://github.com/dparvin/PropertyGridHelpers/wiki/73ec243d-2005-9d6b-8d20-bfc12895eec6">Pro
 33        public static string[] GetResourcesNames(Type enumType)
 2034        {
 35#if NET5_0_OR_GREATER
 1236            ArgumentNullException.ThrowIfNull(enumType);
 37#else
 1638            if (enumType == null)
 1639                throw new ArgumentNullException(nameof(enumType));
 40#endif
 41
 2442            if (!enumType.IsEnum)
 2043                throw new ArgumentException("The provided type must be an enum.", nameof(enumType));
 44
 45            // Get the assembly where the enum type is declared
 2046            var assembly = enumType.Assembly;
 47
 48            // Get all embedded resources in the assembly
 2049            var resourceNames = assembly.GetManifestResourceNames();
 50
 2051            return resourceNames;
 1252        }
 53
 54        /// <summary>
 55        /// Analyzes the resources embedded within a given assembly and prints details  about their type and structure.
 56        /// </summary>
 57        /// <param name="assembly">The assembly to inspect for embedded resources.</param>
 58        /// <exception cref="ArgumentNullException">
 59        /// Thrown if <paramref name="assembly"/> is <c>null</c>.
 60        /// </exception>
 61        /// <remarks>
 62        /// This method examines the specified <paramref name="assembly"/> and identifies:
 63        /// <list type="bullet">
 64        /// <item><description>Embedded resources.</description></item>
 65        /// <item><description>Compiled resource files (<c>.resources</c> files).</description></item>
 66        /// </list>
 67        /// The results are written to the standard output (console).
 68        /// <para><b>Example Output:</b></para>
 69        /// <pre>
 70        /// Checking resources in assembly:
 71        ///   Embedded Resources: - MyNamespace.MyResource.txt (Embedded Resource)
 72        ///
 73        /// Resource Files:
 74        /// - MyNamespace.Strings.resources (Resource File)
 75        /// -> WelcomeMessage: System.String
 76        /// -> AppVersion: System.Int32
 77        /// </pre>
 78        ///
 79        /// <para>If a compiled resource file (<c>.resources</c>) is found, this method also
 80        /// attempts to deserialize its contents and print the key-value pairs along with their data types.</para>
 81        ///
 82        /// <para><b>Note:</b> This method is intended primarily for debugging and inspection purposes. It may not be
 83        /// suitable for use in production applications.</para>
 84        /// </remarks>
 85        public static void CheckResourceType(Assembly assembly)
 1686        {
 87#if NET5_0_OR_GREATER
 888            ArgumentNullException.ThrowIfNull(assembly);
 89#else
 1690            if (assembly == null)
 1691                throw new ArgumentNullException(nameof(assembly));
 92#endif
 93
 2094            Console.WriteLine("Checking resources in assembly:");
 95
 96            // Check for embedded resources
 2097            var embeddedResources = assembly.GetManifestResourceNames();
 2098            Console.WriteLine("\nEmbedded Resources:");
 6099            foreach (var resource in embeddedResources)
 24100            {
 32101                Console.WriteLine($"- {resource} (Embedded Resource)");
 24102            }
 103
 104            // Check for resource files (.resx compiled to .resources)
 20105            Console.WriteLine("\nResource Files:");
 52106            foreach (var resourceName in embeddedResources.Where(
 36107                r => r.EndsWith(".resources", StringComparison.InvariantCulture)))
 20108            {
 28109                Console.WriteLine($"- {resourceName} (Resource File)");
 110#if NET5_0_OR_GREATER
 12111                using var stream = assembly.GetManifestResourceStream(resourceName);
 12112                if (stream != null)
 12113                {
 12114                    using var reader = new System.Resources.Extensions.DeserializingResourceReader(stream);
 180115                    foreach (DictionaryEntry entry in reader)
 72116                        Console.WriteLine($"  -> {entry.Key}: {entry.Value.GetType().FullName}");
 12117                }
 118#else
 119
 16120                using (var stream = assembly.GetManifestResourceStream(resourceName))
 16121                    if (stream != null)
 16122                        using (var reader = new ResourceReader(stream))
 16123                            foreach (DictionaryEntry entry in reader)
 16124                                Console.WriteLine($"  -> {entry.Key}: {entry.Value.GetType().FullName}");
 125#endif
 20126            }
 20127        }
 128
 129        /// <summary>
 130        /// Retrieves a localized string from a resource file based on the specified resource key.
 131        /// </summary>
 132        /// <param name="resourceKey">The key identifying the resource string.</param>
 133        /// <param name="culture">The culture.</param>
 134        /// <param name="resourceSource">The type of the resource class that contains the resource file.</param>
 135        /// <returns>
 136        /// The localized string corresponding to <paramref name="resourceKey" /> from the specified <paramref name="res
 137        /// key itself.
 138        /// </returns>
 139        /// <exception cref="ArgumentNullException">Thrown if <paramref name="resourceKey" /> or <paramref name="resourc
 140        /// <remarks>
 141        /// This method uses a <see cref="ResourceManager" /> to retrieve the localized string based on the current
 142        /// culture. If the resource key does not exist in the specified resource file, the method returns the key
 143        /// itself instead of throwing an exception.
 144        /// </remarks>
 145        /// <example>
 146        /// Example usage:
 147        /// <code>
 148        /// string message = GetResourceString("WelcomeMessage", typeof(Resources.Messages));
 149        /// Console.WriteLine(message); // Outputs localized message or "WelcomeMessage" if not found
 150        /// </code>
 151        /// </example>
 152        public static string GetResourceString(string resourceKey, CultureInfo culture, Type resourceSource)
 64153        {
 72154            if (string.IsNullOrEmpty(resourceKey))
 20155                throw new ArgumentNullException(nameof(resourceKey));
 156#if NET8_0_OR_GREATER
 52157            ArgumentNullException.ThrowIfNull(resourceSource);
 158#else
 159
 16160            if (resourceSource == null)
 16161                throw new ArgumentNullException(nameof(resourceSource));
 162#endif
 163
 64164            var resourceManager = new ResourceManager(resourceSource);
 165
 166            try
 56167            {
 64168                var result = resourceManager.GetString(resourceKey, culture ?? CultureInfo.CurrentCulture);
 60169                if (result == null)
 12170                    Debug.WriteLine($"[Localization] Missing resource key: '{resourceKey}' in {resourceSource.FullName}"
 171
 60172                return result ?? resourceKey;
 173            }
 20174            catch (MissingManifestResourceException)
 12175            {
 20176                return resourceKey; // Fallback if resource file is missing
 177            }
 64178        }
 179
 180        /// <summary>
 181        /// Determines the resource path based on the specified property or related data type,
 182        /// filtered by the specified <see cref="ResourceUsage" />.
 183        /// </summary>
 184        /// <param name="context">
 185        /// The type descriptor context, which provides metadata about the property and its container.
 186        /// </param>
 187        /// <param name="type">
 188        /// The data type associated with the resource, typically an enum or a property type.
 189        /// </param>
 190        /// <param name="resourceUsage">
 191        /// The kind of resource to resolve (e.g., <see cref="ResourceUsage.Strings"/>,
 192        /// <see cref="ResourceUsage.Images"/>). This helps determine the most appropriate resource path
 193        /// when multiple paths are defined.
 194        /// </param>
 195        /// <returns>
 196        /// A string containing the resource path based on the provided property or type.
 197        /// If no applicable attributes are found, the method returns the default resource path:
 198        /// <c>"Properties.Resources"</c>.
 199        /// </returns>
 200        /// <exception cref="ArgumentNullException">
 201        /// Thrown if <paramref name="type" /> is <c>null</c>.
 202        /// </exception>
 203        /// <exception cref="ArgumentException">
 204        /// Thrown if <paramref name="resourceUsage" /> is <see cref="ResourceUsage.None"/>.
 205        /// </exception>
 206        /// <remarks>
 207        /// This method searches for the resource path using the following order of precedence:
 208        /// <list type="number">
 209        /// <item>
 210        /// If the property has a <see cref="DynamicPathSourceAttribute"/> matching the given
 211        /// <paramref name="resourceUsage"/>, it retrieves the path from the referenced property.
 212        /// </item>
 213        /// <item>
 214        /// If the property or its type has a <see cref="ResourcePathAttribute"/> matching the specified
 215        /// <paramref name="resourceUsage"/>, it uses the defined path.
 216        /// </item>
 217        /// <item>
 218        /// If the type (or its underlying nullable type) is an enumeration and has a matching
 219        /// <see cref="ResourcePathAttribute"/>, it uses the associated path.
 220        /// </item>
 221        /// <item>
 222        /// If none of the above conditions are met, it defaults to <c>"Properties.Resources"</c>.
 223        /// </item>
 224        /// </list>
 225        /// </remarks>
 226        /// <example>
 227        /// Example usage with static path:
 228        /// <code>
 229        /// [ResourcePath("Custom.Resources", resourceUsage: ResourceUsage.Strings)]
 230        /// public enum MyEnum
 231        /// {
 232        ///     Value1,
 233        ///     Value2
 234        /// }
 235        ///
 236        /// string path = GetResourcePath(null, typeof(MyEnum), ResourceUsage.Strings);
 237        /// Console.WriteLine(path); // Outputs: "Custom.Resources"
 238        /// </code>
 239        ///
 240        /// Example usage with dynamic path:
 241        /// <code>
 242        /// [DynamicPathSource(nameof(MyResourcePath), ResourceUsage.Images)]
 243        /// public MyEnum ImageSelector { get; set; }
 244        ///
 245        /// public string MyResourcePath => "Dynamic.Image.Resources";
 246        /// </code>
 247        /// </example>
 248        public static string GetResourcePath(ITypeDescriptorContext context, Type type, ResourceUsage resourceUsage = Re
 116249        {
 250            // Check the context for a dynamic path
 124251            if (context?.Instance != null && context.PropertyDescriptor != null)
 48252            {
 253                // 1. Check for DynamicPathSourceAttribute for the given usage
 56254                var dynamicAttr = DynamicPathSourceAttribute.Get(context, resourceUsage);
 56255                if (dynamicAttr != null)
 20256                {
 28257                    var sourceProp = context.Instance.GetType().GetProperty(dynamicAttr.PathPropertyName);
 28258                    if (sourceProp?.PropertyType == typeof(string))
 20259                        return sourceProp.GetValue(context.Instance, null) as string;
 16260                }
 261                // 2. Check for ResourcePathAttribute for the given usage
 52262                var pathAttr = ResourcePathAttribute.Get(context, resourceUsage);
 52263                if (pathAttr != null)
 32264                    return pathAttr.ResourcePath;
 28265            }
 266
 267            // 3. Check for ResourcePathAttribute on the enum type (if applicable)
 104268            if (type != null)
 96269            {
 104270                var enumType = Nullable.GetUnderlyingType(type) ?? type;
 104271                if (enumType.IsEnum)
 80272                {
 88273                    var enumAttrs = enumType.GetCustomAttributes(typeof(ResourcePathAttribute), true)
 88274                                            .OfType<ResourcePathAttribute>();
 144275                    var enumAttr = enumAttrs.FirstOrDefault(attr => (attr.ResourceUsage & resourceUsage) != 0);
 88276                    if (enumAttr != null)
 72277                        return enumAttr.ResourcePath;
 24278                }
 40279            }
 280
 281            // 4. Fallback default
 48282            return "Properties.Resources";
 116283        }
 284
 285        /// <summary>
 286        /// Retrieves the file extension associated with a property, if specified.
 287        /// </summary>
 288        /// <param name="context">
 289        /// The type descriptor context, which provides metadata about the property and its container.
 290        /// </param>
 291        /// <returns>
 292        /// A string containing the file extension for the resource.  If no valid extension is found, returns an empty
 293        /// string.
 294        /// </returns>
 295        /// <remarks>
 296        /// This method determines the file extension based on the following order of precedence:
 297        /// <list type="number">
 298        /// <item>Checks if the property has a <see cref="FileExtensionAttribute"/> and retrieves the
 299        /// value of the property it references.</item>
 300        /// <item>If the referenced property is a string, its value is returned.</item>
 301        /// <item>If the referenced property is an enumeration:
 302        /// <list type="bullet">
 303        /// <item>Returns the enum's string representation, unless it is <c>None</c>, in which case an empty string is
 304        /// returned.</item>
 305        /// <item>If the enum field has an <see cref="EnumTextAttribute"/>, returns its custom text value.</item>
 306        /// <item>If the enum field has a <see cref="LocalizedEnumTextAttribute"/>, returns its localized
 307        /// text value.</item>
 308        /// </list>
 309        /// </item>
 310        /// <item>If no matching attributes are found, the method returns an empty string.</item>
 311        /// </list>
 312        ///
 313        /// Normally a user would not call this method directly, but it is  used by the UIEditors
 314        /// to load values into the <see cref="PropertyGrid"/>.
 315        /// </remarks>
 316        /// <exception cref="InvalidOperationException">
 317        /// Thrown if the referenced property is not found or is not public.
 318        /// </exception>
 319        /// <example>
 320        /// Example usage:
 321        /// <code>
 322        /// [FileExtension(nameof(FileType))] public string FileName { get; set; } = "example";
 323        /// public string FileType { get; set; } = "xml";
 324        ///
 325        /// var PropertyDescriptor = TypeDescriptor.GetProperties(this)[nameof(FileName)];
 326        /// var context = new CustomTypeDescriptorContext(PropertyDescriptor, this);
 327        /// string extension = GetFileExtension(context);
 328        /// Console.WriteLine(extension); // Outputs: "xml"
 329        /// </code>
 330        /// </example>
 331        public static string GetFileExtension(ITypeDescriptorContext context)
 52332        {
 60333            if (context != null)
 48334            {
 56335                var FileExtensionAttr = FileExtensionAttribute.Get(context);
 56336                if (FileExtensionAttr != null)
 48337                {
 338                    // Find the referenced property
 56339                    var fileExtensionProperty = GetRequiredProperty(context.Instance, FileExtensionAttr.PropertyName);
 48340                    if (fileExtensionProperty.PropertyType == typeof(string))
 12341                    {
 342                        // Return the value of the referenced property
 20343                        return fileExtensionProperty.GetValue(context.Instance, null) as string;
 344                    }
 44345                    else if (fileExtensionProperty.PropertyType.IsEnum ||
 44346                        (Nullable.GetUnderlyingType(fileExtensionProperty.PropertyType) is Type underlyingType &&
 44347                            underlyingType.IsEnum))
 32348                    {
 349                        // Get the property value.
 40350                        var rawValue = fileExtensionProperty.GetValue(context.Instance, null);
 40351                        if (rawValue == null)
 352                            // If the value is null, return an empty string (or handle as needed).
 20353                            return string.Empty;
 354
 355                        // At this point rawValue should be an enum value.
 36356                        var extension = (Enum)rawValue;
 36357                        var enumField = extension.GetType().GetField(extension.ToString());
 36358                        if (enumField != null)
 28359                        {
 36360                            var enumTextAttr = enumField.GetCustomAttributes(typeof(EnumTextAttribute), false) as EnumTe
 36361                            if (enumTextAttr.Length > 0)
 24362                                return enumTextAttr[0].EnumText; // Return custom text
 20363                        }
 364                        // Return the value of the referenced property
 28365                        return (string.IsNullOrEmpty(extension.ToString()) ||
 28366                                string.Equals(extension.ToString(), "None", StringComparison.OrdinalIgnoreCase))
 28367                            ? string.Empty
 28368                            : extension.ToString();
 369                    }
 12370                }
 12371            }
 372
 24373            return string.Empty;
 44374        }
 375
 376        /// <summary>
 377        /// Retrieves a required public instance property from the specified object by name.
 378        /// </summary>
 379        /// <param name="instance">
 380        /// The object instance whose property should be retrieved. Must not be <c>null</c>.
 381        /// </param>
 382        /// <param name="propertyName">
 383        /// The name of the property to look up. Case-sensitive.
 384        /// </param>
 385        /// <returns>
 386        /// The <see cref="PropertyInfo"/> representing the requested public property if it exists.
 387        /// </returns>
 388        /// <exception cref="InvalidOperationException">
 389        /// Thrown if the specified property does not exist on the type of <paramref name="instance"/>,
 390        /// or if the property exists but is not publicly accessible.
 391        /// </exception>
 392        /// <remarks>
 393        /// This method uses reflection to locate a property by name, including non-public declarations,
 394        /// but enforces that the property has a public getter.
 395        /// If the property cannot be found or fails the public visibility requirement,
 396        /// an <see cref="InvalidOperationException"/> is thrown.
 397        /// </remarks>
 398        private static PropertyInfo GetRequiredProperty(object instance, string propertyName)
 48399        {
 56400            var property = instance.GetType()
 56401                    .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ??
 56402                throw new InvalidOperationException(
 56403                    $"Property '{propertyName}' not found on type '{instance.GetType()}'.");
 404
 405#if NET35
 406            if (property.GetGetMethod() == null)
 407#else
 52408            if (!property.GetMethod.IsPublic)
 409#endif
 410
 12411            {
 20412                throw new InvalidOperationException(
 20413                    $"Property '{propertyName}' on type '{instance.GetType()}' must be public.");
 414            }
 415
 48416            return property;
 40417        }
 418
 419#if NET35
 420        /// <summary>
 421        /// Retrieves the first custom attribute of the specified type applied to the given <see cref="MemberInfo"/>.
 422        /// </summary>
 423        /// <typeparam name="T">
 424        /// The type of attribute to retrieve. Must derive from <see cref="Attribute"/>.
 425        /// </typeparam>
 426        /// <param name="member">
 427        /// The member (e.g., property, method, field, type) to inspect for the attribute.
 428        /// </param>
 429        /// <returns>
 430        /// An instance of the attribute type <typeparamref name="T"/> if one is defined on the member; otherwise, <c>nu
 431        /// </returns>
 432        /// <remarks>
 433        /// this uses <see cref="MemberInfo.GetCustomAttributes(Type, bool)"/> internally.
 434        /// </remarks>
 435        /// <exception cref="ArgumentNullException">
 436        /// Thrown if <paramref name="member"/> is <c>null</c>.
 437        /// </exception>
 438        /// <example>
 439        /// Example usage:
 440        /// <code>
 441        /// var property = typeof(MyClass).GetProperty("MyProperty");
 442        /// var attr = Support.GetFirstCustomAttribute&lt;AutoCompleteSetupAttribute&gt;(property);
 443        /// if (attr != null)
 444        /// {
 445        ///     // Attribute was found
 446        /// }
 447        /// </code>
 448        /// </example>
 449#else
 450        /// <summary>
 451        /// Retrieves the first custom attribute of the specified type applied to the given <see cref="MemberInfo"/>.
 452        /// </summary>
 453        /// <typeparam name="T">
 454        /// The type of attribute to retrieve. Must derive from <see cref="Attribute"/>.
 455        /// </typeparam>
 456        /// <param name="member">
 457        /// The member (e.g., property, method, field, type) to inspect for the attribute.
 458        /// </param>
 459        /// <returns>
 460        /// An instance of the attribute type <typeparamref name="T"/> if one is defined on the member; otherwise, <c>nu
 461        /// </returns>
 462        /// <remarks>
 463        /// On .NET Framework 3.5, this uses <see cref="MemberInfo.GetCustomAttributes(Type, bool)"/> internally;
 464        /// on later versions, it uses <see cref="CustomAttributeExtensions.GetCustomAttribute{T}(MemberInfo, bool)"/>.
 465        /// </remarks>
 466        /// <exception cref="ArgumentNullException">
 467        /// Thrown if <paramref name="member"/> is <c>null</c>.
 468        /// </exception>
 469        /// <example>
 470        /// Example usage:
 471        /// <code>
 472        /// var property = typeof(MyClass).GetProperty("MyProperty");
 473        /// var attr = Support.GetFirstCustomAttribute&lt;AutoCompleteSetupAttribute&gt;(property);
 474        /// if (attr != null)
 475        /// {
 476        ///     // Attribute was found
 477        /// }
 478        /// </code>
 479        /// </example>
 480#endif
 481        public static T GetFirstCustomAttribute<T>(MemberInfo member) where T : Attribute
 748482        {
 483#if NET5_0_OR_GREATER
 740484            ArgumentNullException.ThrowIfNull(member);
 485#else
 16486            if (member == null)
 16487                throw new ArgumentNullException(nameof(member));
 488#endif
 489#if NET35
 490            var attrs = member.GetCustomAttributes(typeof(T), true);
 491            return attrs.Length > 0 ? (T)attrs[0] : null;
 492#else
 752493            return member.GetCustomAttribute<T>(true);
 494#endif
 744495        }
 496
 497        /// <summary>
 498        /// Retrieves the <see cref="FieldInfo"/> for a specific enum value.
 499        /// </summary>
 500        /// <param name="value">The enum value to inspect.</param>
 501        /// <returns>
 502        /// The <see cref="FieldInfo"/> corresponding to the specified enum value, or <c>null</c>
 503        /// if the value does not match a defined member of the enum type.
 504        /// </returns>
 505        /// <exception cref="ArgumentNullException">
 506        /// Thrown when <paramref name="value"/> is <c>null</c>.
 507        /// </exception>
 508        public static FieldInfo GetEnumField(Enum value)
 576509        {
 510#if NET5_0_OR_GREATER
 568511            ArgumentNullException.ThrowIfNull(value);
 512#else
 16513            if (value == null)
 16514                throw new ArgumentNullException(nameof(value));
 515#endif
 516
 580517            var type = value.GetType();
 580518            var name = Enum.GetName(type, value);
 580519            return name == null ? null : type.GetField(name);
 572520        }
 521
 522        /// <summary>
 523        /// Retrieves the <see cref="PropertyInfo"/> for the property described by the specified <see
 524        /// cref="ITypeDescriptorContext"/>.
 525        /// </summary>
 526        /// <param name="context">
 527        /// The type descriptor context containing metadata about the property, including its name and declaring
 528        /// component type.
 529        /// </param>
 530        /// <returns>
 531        /// A <see cref="PropertyInfo"/> object representing the property described by <paramref name="context"/>.
 532        /// </returns>
 533        /// <exception cref="ArgumentNullException">
 534        /// Thrown when <paramref name="context"/> is <c>null</c>.
 535        /// </exception>
 536        /// <exception cref="ArgumentException">
 537        /// Thrown when <paramref name="context"/> does not contain a valid <see cref="PropertyDescriptor"/>.
 538        /// </exception>
 539        /// <exception cref="InvalidOperationException">
 540        /// Thrown when the property described by the context could not be found on the component type.
 541        /// </exception>
 542        /// <example>
 543        /// Example usage: <code> var context = TypeDescriptorContext.Create(typeof(MyClass), "MyProperty");
 544        /// PropertyInfo info = Support.GetPropertyInfo(context);</code>
 545        /// </example>
 546        public static PropertyInfo GetPropertyInfo(ITypeDescriptorContext context)
 248547        {
 548#if NET5_0_OR_GREATER
 240549            ArgumentNullException.ThrowIfNull(context);
 550#else
 16551            if (context == null)
 16552                throw new ArgumentNullException(nameof(context));
 553#endif
 252554            if (context.PropertyDescriptor == null)
 20555                throw new ArgumentException("Context does not contain a valid PropertyDescriptor.", nameof(context));
 556
 248557            var componentType = context.PropertyDescriptor.ComponentType;
 248558            var propertyName = context.PropertyDescriptor.Name;
 248559            var propInfo = componentType.GetProperty(propertyName) ??
 248560                throw new InvalidOperationException(
 248561                    $"Property '{propertyName}' not found on type {componentType.FullName}.");
 240562            return propInfo;
 232563        }
 564
 565        /// <summary>
 566        /// Sets the current thread's culture and UI culture to the specified language identifier.
 567        /// </summary>
 568        /// <param name="language">
 569        /// The language code (e.g., <c>"en-US"</c>, <c>"fr-FR"</c>, <c>"de-DE"</c>) to apply to
 570        /// <see cref="Thread.CurrentCulture"/> and <see cref="Thread.CurrentUICulture"/>.
 571        /// </param>
 572        /// <remarks>
 573        /// This method is intended primarily for testing and debugging localization or globalization scenarios,
 574        /// allowing the current thread to simulate running under a specific culture.
 575        /// </remarks>
 576        public static void SetLanguage(string language)
 12577        {
 20578            Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
 20579            Thread.CurrentThread.CurrentUICulture = new CultureInfo(language);
 20580        }
 581    }
 582}