< Summary - PropertyGridHelpers Code Coverage

Information
Class: PropertyGridHelpers.Support.Support
Assembly: PropertyGridHelpers
File(s): C:\Agent\_work\2\s\Code\PropertyGridHelpers\Support\Support.cs
Tag: PropertyGridHelpers Build_2026.1.11.1_#580
Line coverage
100%
Covered lines: 169
Uncovered lines: 0
Coverable lines: 169
Total lines: 601
Line coverage: 100%
Branch coverage
100%
Covered branches: 76
Total branches: 76
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/4/2025 - 9:39:23 PM Line coverage: 100% (143/143) Branch coverage: 92.2% (83/90) Total lines: 437 Tag: PropertyGridHelpers Build_2025.3.4.1_#3774/2/2025 - 8:52:04 PM Line coverage: 76.3% (110/144) Branch coverage: 82.9% (68/82) Total lines: 444 Tag: PropertyGridHelpers Build_2025.4.2.1_#3784/6/2025 - 12:17:16 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.4.6.1_#3795/4/2025 - 8:18:24 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.4.1_#3905/22/2025 - 7:52:21 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.22.1_#4175/23/2025 - 7:21:57 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: Test PropertyGridHelpers Build_2025.5.23.2_#4265/28/2025 - 8:07:03 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.28.1_#4286/3/2025 - 4:27:27 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: Test PropertyGridHelpers Build_2025.6.3.1_#4336/3/2025 - 7:42:47 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.6.3.2_#4396/12/2025 - 9:38:24 PM Line coverage: 98.6% (144/146) Branch coverage: 98.6% (73/74) Total lines: 442 Tag: PropertyGridHelpers Build_2025.6.12.2_#4566/16/2025 - 6:22:26 PM Line coverage: 100% (146/146) Branch coverage: 100% (74/74) Total lines: 442 Tag: PropertyGridHelpers Build_2025.6.16.1_#4626/16/2025 - 8:08:15 PM Line coverage: 99.3% (150/151) Branch coverage: 100% (74/74) Total lines: 480 Tag: PropertyGridHelpers Build_2025.6.16.2_#4636/17/2025 - 12:14:52 AM Line coverage: 100% (157/157) Branch coverage: 100% (76/76) Total lines: 497 Tag: PropertyGridHelpers Build_2025.6.17.1_#4647/4/2025 - 11:01:20 PM Line coverage: 100% (157/157) Branch coverage: 100% (76/76) Total lines: 543 Tag: PropertyGridHelpers Build_2025.7.4.1_#4777/9/2025 - 2:20:26 PM Line coverage: 100% (166/166) Branch coverage: 100% (86/86) Total lines: 558 Tag: PropertyGridHelpers Build_2025.7.9.1_#4807/14/2025 - 8:32:11 PM Line coverage: 100% (159/159) Branch coverage: 98.6% (71/72) Total lines: 582 Tag: PropertyGridHelpers Build_2025.7.14.3_#4837/15/2025 - 9:09:03 PM Line coverage: 100% (159/159) Branch coverage: 100% (72/72) Total lines: 582 Tag: PropertyGridHelpers Build_2025.7.15.1_#4851/10/2026 - 4:46:27 PM Line coverage: 100% (169/169) Branch coverage: 100% (76/76) Total lines: 601 Tag: PropertyGridHelpers Build_2026.1.10.1_#579 3/4/2025 - 9:39:23 PM Line coverage: 100% (143/143) Branch coverage: 92.2% (83/90) Total lines: 437 Tag: PropertyGridHelpers Build_2025.3.4.1_#3774/2/2025 - 8:52:04 PM Line coverage: 76.3% (110/144) Branch coverage: 82.9% (68/82) Total lines: 444 Tag: PropertyGridHelpers Build_2025.4.2.1_#3784/6/2025 - 12:17:16 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.4.6.1_#3795/4/2025 - 8:18:24 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.4.1_#3905/22/2025 - 7:52:21 PM Line coverage: 100% (119/119) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.22.1_#4175/23/2025 - 7:21:57 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: Test PropertyGridHelpers Build_2025.5.23.2_#4265/28/2025 - 8:07:03 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.5.28.1_#4286/3/2025 - 4:27:27 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: Test PropertyGridHelpers Build_2025.6.3.1_#4336/3/2025 - 7:42:47 PM Line coverage: 100% (130/130) Branch coverage: 100% (70/70) Total lines: 392 Tag: PropertyGridHelpers Build_2025.6.3.2_#4396/12/2025 - 9:38:24 PM Line coverage: 98.6% (144/146) Branch coverage: 98.6% (73/74) Total lines: 442 Tag: PropertyGridHelpers Build_2025.6.12.2_#4566/16/2025 - 6:22:26 PM Line coverage: 100% (146/146) Branch coverage: 100% (74/74) Total lines: 442 Tag: PropertyGridHelpers Build_2025.6.16.1_#4626/16/2025 - 8:08:15 PM Line coverage: 99.3% (150/151) Branch coverage: 100% (74/74) Total lines: 480 Tag: PropertyGridHelpers Build_2025.6.16.2_#4636/17/2025 - 12:14:52 AM Line coverage: 100% (157/157) Branch coverage: 100% (76/76) Total lines: 497 Tag: PropertyGridHelpers Build_2025.6.17.1_#4647/4/2025 - 11:01:20 PM Line coverage: 100% (157/157) Branch coverage: 100% (76/76) Total lines: 543 Tag: PropertyGridHelpers Build_2025.7.4.1_#4777/9/2025 - 2:20:26 PM Line coverage: 100% (166/166) Branch coverage: 100% (86/86) Total lines: 558 Tag: PropertyGridHelpers Build_2025.7.9.1_#4807/14/2025 - 8:32:11 PM Line coverage: 100% (159/159) Branch coverage: 98.6% (71/72) Total lines: 582 Tag: PropertyGridHelpers Build_2025.7.14.3_#4837/15/2025 - 9:09:03 PM Line coverage: 100% (159/159) Branch coverage: 100% (72/72) Total lines: 582 Tag: PropertyGridHelpers Build_2025.7.15.1_#4851/10/2026 - 4:46:27 PM Line coverage: 100% (169/169) Branch coverage: 100% (76/76) Total lines: 601 Tag: PropertyGridHelpers Build_2026.1.10.1_#579

Metrics

File(s)

C:\Agent\_work\2\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        /// <param name="throwOnError">
 196        /// if set to <c>true</c> throw on error when the Dynamic Path Attribute is not setup correctly.
 197        /// </param>
 198        /// <returns>
 199        /// A string containing the resource path based on the provided property or type.
 200        /// If no applicable attributes are found, the method returns the default resource path:
 201        /// <c>"Properties.Resources"</c>.
 202        /// </returns>
 203        /// <exception cref="ArgumentNullException">
 204        /// Thrown if <paramref name="type" /> is <c>null</c>.
 205        /// </exception>
 206        /// <exception cref="ArgumentException">
 207        /// Thrown if <paramref name="resourceUsage" /> is <see cref="ResourceUsage.None" />.
 208        /// </exception>
 209        /// <remarks>
 210        /// This method searches for the resource path using the following order of precedence:
 211        /// <list type="number">
 212        /// <item>
 213        /// If the property has a <see cref="DynamicPathSourceAttribute" /> matching the given
 214        /// <paramref name="resourceUsage" />, it retrieves the path from the referenced property.
 215        /// </item>
 216        /// <item>
 217        /// If the property or its type has a <see cref="ResourcePathAttribute" /> matching the specified
 218        /// <paramref name="resourceUsage" />, it uses the defined path.
 219        /// </item>
 220        /// <item>
 221        /// If the type (or its underlying nullable type) is an enumeration and has a matching
 222        /// <see cref="ResourcePathAttribute" />, it uses the associated path.
 223        /// </item>
 224        /// <item>
 225        /// If none of the above conditions are met, it defaults to <c>"Properties.Resources"</c>.
 226        /// </item>
 227        /// </list>
 228        /// </remarks>
 229        /// <example>
 230        /// Example usage with static path:
 231        /// <code>
 232        ///     [ResourcePath("Custom.Resources", resourceUsage: ResourceUsage.Strings)]
 233        ///     public enum MyEnum
 234        ///     {
 235        ///         Value1,
 236        ///         Value2
 237        ///     }
 238        ///
 239        ///     string path = GetResourcePath(null, typeof(MyEnum), ResourceUsage.Strings);
 240        ///     Console.WriteLine(path); // Outputs: "Custom.Resources"
 241        /// </code>
 242        ///
 243        /// Example usage with dynamic path:
 244        /// <code>
 245        ///     [DynamicPathSource(nameof(MyResourcePath), ResourceUsage.Images)]
 246        ///     public MyEnum ImageSelector { get; set; }
 247        ///
 248        ///     public string MyResourcePath =&gt; "Dynamic.Image.Resources";
 249        /// </code>
 250        /// </example>
 251        public static string GetResourcePath(
 252            ITypeDescriptorContext context,
 253            Type type,
 254            ResourceUsage resourceUsage = ResourceUsage.All,
 255            bool throwOnError = false)
 124256        {
 257            // Check the context for a dynamic path
 132258            if (context?.Instance != null &&
 132259                context.PropertyDescriptor != null)
 56260            {
 261                // 1. Check for DynamicPathSourceAttribute for the given usage
 64262                var dynamicAttr = DynamicPathSourceAttribute.Get(context, resourceUsage);
 64263                if (dynamicAttr != null)
 28264                {
 36265                    var sourceProp = context.Instance.GetType().GetProperty(dynamicAttr.PathPropertyName);
 36266                    if (sourceProp == null)
 16267                    {
 24268                        if (throwOnError)
 20269                            throw new MissingMemberException($"The '{dynamicAttr.PathPropertyName}' property is not defi
 12270                    }
 271                    else
 20272                    {
 28273                        if (sourceProp.PropertyType == typeof(string))
 20274                            return sourceProp.GetValue(context.Instance, null) as string;
 24275                        else if (throwOnError)
 20276                            throw new InvalidOperationException($"The property '{dynamicAttr.PathPropertyName}' on type 
 12277                    }
 278
 16279                }
 280                // 2. Check for ResourcePathAttribute for the given usage
 52281                var pathAttr = ResourcePathAttribute.Get(context, resourceUsage);
 52282                if (pathAttr != null)
 32283                    return pathAttr.ResourcePath;
 28284            }
 285
 286            // 3. Check for ResourcePathAttribute on the enum type (if applicable)
 104287            if (type != null)
 96288            {
 104289                var enumType = Nullable.GetUnderlyingType(type) ?? type;
 104290                if (enumType.IsEnum)
 80291                {
 88292                    var enumAttrs = enumType.GetCustomAttributes(typeof(ResourcePathAttribute), true)
 88293                                            .OfType<ResourcePathAttribute>();
 144294                    var enumAttr = enumAttrs.FirstOrDefault(attr => (attr.ResourceUsage & resourceUsage) != 0);
 88295                    if (enumAttr != null)
 72296                        return enumAttr.ResourcePath;
 24297                }
 40298            }
 299
 300            // 4. Fallback default
 48301            return "Properties.Resources";
 116302        }
 303
 304        /// <summary>
 305        /// Retrieves the file extension associated with a property, if specified.
 306        /// </summary>
 307        /// <param name="context">
 308        /// The type descriptor context, which provides metadata about the property and its container.
 309        /// </param>
 310        /// <returns>
 311        /// A string containing the file extension for the resource.  If no valid extension is found, returns an empty
 312        /// string.
 313        /// </returns>
 314        /// <remarks>
 315        /// This method determines the file extension based on the following order of precedence:
 316        /// <list type="number">
 317        /// <item>Checks if the property has a <see cref="FileExtensionAttribute"/> and retrieves the
 318        /// value of the property it references.</item>
 319        /// <item>If the referenced property is a string, its value is returned.</item>
 320        /// <item>If the referenced property is an enumeration:
 321        /// <list type="bullet">
 322        /// <item>Returns the enum's string representation, unless it is <c>None</c>, in which case an empty string is
 323        /// returned.</item>
 324        /// <item>If the enum field has an <see cref="EnumTextAttribute"/>, returns its custom text value.</item>
 325        /// <item>If the enum field has a <see cref="LocalizedEnumTextAttribute"/>, returns its localized
 326        /// text value.</item>
 327        /// </list>
 328        /// </item>
 329        /// <item>If no matching attributes are found, the method returns an empty string.</item>
 330        /// </list>
 331        ///
 332        /// Normally a user would not call this method directly, but it is  used by the UIEditors
 333        /// to load values into the <see cref="PropertyGrid"/>.
 334        /// </remarks>
 335        /// <exception cref="InvalidOperationException">
 336        /// Thrown if the referenced property is not found or is not public.
 337        /// </exception>
 338        /// <example>
 339        /// Example usage:
 340        /// <code>
 341        /// [FileExtension(nameof(FileType))] public string FileName { get; set; } = "example";
 342        /// public string FileType { get; set; } = "xml";
 343        ///
 344        /// var PropertyDescriptor = TypeDescriptor.GetProperties(this)[nameof(FileName)];
 345        /// var context = new CustomTypeDescriptorContext(PropertyDescriptor, this);
 346        /// string extension = GetFileExtension(context);
 347        /// Console.WriteLine(extension); // Outputs: "xml"
 348        /// </code>
 349        /// </example>
 350        public static string GetFileExtension(ITypeDescriptorContext context)
 52351        {
 60352            if (context != null)
 48353            {
 56354                var FileExtensionAttr = FileExtensionAttribute.Get(context);
 56355                if (FileExtensionAttr != null)
 48356                {
 357                    // Find the referenced property
 56358                    var fileExtensionProperty = GetRequiredProperty(context.Instance, FileExtensionAttr.PropertyName);
 48359                    if (fileExtensionProperty.PropertyType == typeof(string))
 12360                    {
 361                        // Return the value of the referenced property
 20362                        return fileExtensionProperty.GetValue(context.Instance, null) as string;
 363                    }
 44364                    else if (fileExtensionProperty.PropertyType.IsEnum ||
 44365                        (Nullable.GetUnderlyingType(fileExtensionProperty.PropertyType) is Type underlyingType &&
 44366                            underlyingType.IsEnum))
 32367                    {
 368                        // Get the property value.
 40369                        var rawValue = fileExtensionProperty.GetValue(context.Instance, null);
 40370                        if (rawValue == null)
 371                            // If the value is null, return an empty string (or handle as needed).
 20372                            return string.Empty;
 373
 374                        // At this point rawValue should be an enum value.
 36375                        var extension = (Enum)rawValue;
 36376                        var enumField = extension.GetType().GetField(extension.ToString());
 36377                        if (enumField != null)
 28378                        {
 36379                            var enumTextAttr = enumField.GetCustomAttributes(typeof(EnumTextAttribute), false) as EnumTe
 36380                            if (enumTextAttr.Length > 0)
 24381                                return enumTextAttr[0].EnumText; // Return custom text
 20382                        }
 383                        // Return the value of the referenced property
 28384                        return (string.IsNullOrEmpty(extension.ToString()) ||
 28385                                string.Equals(extension.ToString(), "None", StringComparison.OrdinalIgnoreCase))
 28386                            ? string.Empty
 28387                            : extension.ToString();
 388                    }
 12389                }
 12390            }
 391
 24392            return string.Empty;
 44393        }
 394
 395        /// <summary>
 396        /// Retrieves a required public instance property from the specified object by name.
 397        /// </summary>
 398        /// <param name="instance">
 399        /// The object instance whose property should be retrieved. Must not be <c>null</c>.
 400        /// </param>
 401        /// <param name="propertyName">
 402        /// The name of the property to look up. Case-sensitive.
 403        /// </param>
 404        /// <returns>
 405        /// The <see cref="PropertyInfo"/> representing the requested public property if it exists.
 406        /// </returns>
 407        /// <exception cref="InvalidOperationException">
 408        /// Thrown if the specified property does not exist on the type of <paramref name="instance"/>,
 409        /// or if the property exists but is not publicly accessible.
 410        /// </exception>
 411        /// <remarks>
 412        /// This method uses reflection to locate a property by name, including non-public declarations,
 413        /// but enforces that the property has a public getter.
 414        /// If the property cannot be found or fails the public visibility requirement,
 415        /// an <see cref="InvalidOperationException"/> is thrown.
 416        /// </remarks>
 417        private static PropertyInfo GetRequiredProperty(object instance, string propertyName)
 48418        {
 56419            var property = instance.GetType()
 56420                    .GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ??
 56421                throw new InvalidOperationException(
 56422                    $"Property '{propertyName}' not found on type '{instance.GetType()}'.");
 423
 424#if NET35
 425            if (property.GetGetMethod() == null)
 426#else
 52427            if (!property.GetMethod.IsPublic)
 428#endif
 429
 12430            {
 20431                throw new InvalidOperationException(
 20432                    $"Property '{propertyName}' on type '{instance.GetType()}' must be public.");
 433            }
 434
 48435            return property;
 40436        }
 437
 438#if NET35
 439        /// <summary>
 440        /// Retrieves the first custom attribute of the specified type applied to the given <see cref="MemberInfo"/>.
 441        /// </summary>
 442        /// <typeparam name="T">
 443        /// The type of attribute to retrieve. Must derive from <see cref="Attribute"/>.
 444        /// </typeparam>
 445        /// <param name="member">
 446        /// The member (e.g., property, method, field, type) to inspect for the attribute.
 447        /// </param>
 448        /// <returns>
 449        /// An instance of the attribute type <typeparamref name="T"/> if one is defined on the member; otherwise, <c>nu
 450        /// </returns>
 451        /// <remarks>
 452        /// this uses <see cref="MemberInfo.GetCustomAttributes(Type, bool)"/> internally.
 453        /// </remarks>
 454        /// <exception cref="ArgumentNullException">
 455        /// Thrown if <paramref name="member"/> is <c>null</c>.
 456        /// </exception>
 457        /// <example>
 458        /// Example usage:
 459        /// <code>
 460        /// var property = typeof(MyClass).GetProperty("MyProperty");
 461        /// var attr = Support.GetFirstCustomAttribute&lt;AutoCompleteSetupAttribute&gt;(property);
 462        /// if (attr != null)
 463        /// {
 464        ///     // Attribute was found
 465        /// }
 466        /// </code>
 467        /// </example>
 468#else
 469        /// <summary>
 470        /// Retrieves the first custom attribute of the specified type applied to the given <see cref="MemberInfo"/>.
 471        /// </summary>
 472        /// <typeparam name="T">
 473        /// The type of attribute to retrieve. Must derive from <see cref="Attribute"/>.
 474        /// </typeparam>
 475        /// <param name="member">
 476        /// The member (e.g., property, method, field, type) to inspect for the attribute.
 477        /// </param>
 478        /// <returns>
 479        /// An instance of the attribute type <typeparamref name="T"/> if one is defined on the member; otherwise, <c>nu
 480        /// </returns>
 481        /// <remarks>
 482        /// On .NET Framework 3.5, this uses <see cref="MemberInfo.GetCustomAttributes(Type, bool)"/> internally;
 483        /// on later versions, it uses <see cref="CustomAttributeExtensions.GetCustomAttribute{T}(MemberInfo, bool)"/>.
 484        /// </remarks>
 485        /// <exception cref="ArgumentNullException">
 486        /// Thrown if <paramref name="member"/> is <c>null</c>.
 487        /// </exception>
 488        /// <example>
 489        /// Example usage:
 490        /// <code>
 491        /// var property = typeof(MyClass).GetProperty("MyProperty");
 492        /// var attr = Support.GetFirstCustomAttribute&lt;AutoCompleteSetupAttribute&gt;(property);
 493        /// if (attr != null)
 494        /// {
 495        ///     // Attribute was found
 496        /// }
 497        /// </code>
 498        /// </example>
 499#endif
 500        public static T GetFirstCustomAttribute<T>(MemberInfo member) where T : Attribute
 748501        {
 502#if NET5_0_OR_GREATER
 740503            ArgumentNullException.ThrowIfNull(member);
 504#else
 16505            if (member == null)
 16506                throw new ArgumentNullException(nameof(member));
 507#endif
 508#if NET35
 509            var attrs = member.GetCustomAttributes(typeof(T), true);
 510            return attrs.Length > 0 ? (T)attrs[0] : null;
 511#else
 752512            return member.GetCustomAttribute<T>(true);
 513#endif
 744514        }
 515
 516        /// <summary>
 517        /// Retrieves the <see cref="FieldInfo"/> for a specific enum value.
 518        /// </summary>
 519        /// <param name="value">The enum value to inspect.</param>
 520        /// <returns>
 521        /// The <see cref="FieldInfo"/> corresponding to the specified enum value, or <c>null</c>
 522        /// if the value does not match a defined member of the enum type.
 523        /// </returns>
 524        /// <exception cref="ArgumentNullException">
 525        /// Thrown when <paramref name="value"/> is <c>null</c>.
 526        /// </exception>
 527        public static FieldInfo GetEnumField(Enum value)
 576528        {
 529#if NET5_0_OR_GREATER
 568530            ArgumentNullException.ThrowIfNull(value);
 531#else
 16532            if (value == null)
 16533                throw new ArgumentNullException(nameof(value));
 534#endif
 535
 580536            var type = value.GetType();
 580537            var name = Enum.GetName(type, value);
 580538            return name == null ? null : type.GetField(name);
 572539        }
 540
 541        /// <summary>
 542        /// Retrieves the <see cref="PropertyInfo"/> for the property described by the specified <see
 543        /// cref="ITypeDescriptorContext"/>.
 544        /// </summary>
 545        /// <param name="context">
 546        /// The type descriptor context containing metadata about the property, including its name and declaring
 547        /// component type.
 548        /// </param>
 549        /// <returns>
 550        /// A <see cref="PropertyInfo"/> object representing the property described by <paramref name="context"/>.
 551        /// </returns>
 552        /// <exception cref="ArgumentNullException">
 553        /// Thrown when <paramref name="context"/> is <c>null</c>.
 554        /// </exception>
 555        /// <exception cref="ArgumentException">
 556        /// Thrown when <paramref name="context"/> does not contain a valid <see cref="PropertyDescriptor"/>.
 557        /// </exception>
 558        /// <exception cref="InvalidOperationException">
 559        /// Thrown when the property described by the context could not be found on the component type.
 560        /// </exception>
 561        /// <example>
 562        /// Example usage: <code> var context = TypeDescriptorContext.Create(typeof(MyClass), "MyProperty");
 563        /// PropertyInfo info = Support.GetPropertyInfo(context);</code>
 564        /// </example>
 565        public static PropertyInfo GetPropertyInfo(ITypeDescriptorContext context)
 256566        {
 567#if NET5_0_OR_GREATER
 248568            ArgumentNullException.ThrowIfNull(context);
 569#else
 16570            if (context == null)
 16571                throw new ArgumentNullException(nameof(context));
 572#endif
 260573            if (context.PropertyDescriptor == null)
 20574                throw new ArgumentException("Context does not contain a valid PropertyDescriptor.", nameof(context));
 575
 256576            var componentType = context.PropertyDescriptor.ComponentType;
 256577            var propertyName = context.PropertyDescriptor.Name;
 256578            var propInfo = componentType.GetProperty(propertyName) ??
 256579                throw new InvalidOperationException(
 256580                    $"Property '{propertyName}' not found on type {componentType.FullName}.");
 248581            return propInfo;
 240582        }
 583
 584        /// <summary>
 585        /// Sets the current thread's culture and UI culture to the specified language identifier.
 586        /// </summary>
 587        /// <param name="language">
 588        /// The language code (e.g., <c>"en-US"</c>, <c>"fr-FR"</c>, <c>"de-DE"</c>) to apply to
 589        /// <see cref="Thread.CurrentCulture"/> and <see cref="Thread.CurrentUICulture"/>.
 590        /// </param>
 591        /// <remarks>
 592        /// This method is intended primarily for testing and debugging localization or globalization scenarios,
 593        /// allowing the current thread to simulate running under a specific culture.
 594        /// </remarks>
 595        public static void SetLanguage(string language)
 12596        {
 20597            Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
 20598            Thread.CurrentThread.CurrentUICulture = new CultureInfo(language);
 20599        }
 600    }
 601}