Repository: dotnet/aspnetcore
File: src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs (C#)

Code before:
    internal static string[] GetCultures(CultureInfo? cultureInfo, CultureInfo? uiCultureInfo = null)
    {
        var culturesToLoad = new HashSet<string>();

        // Once WASM is ready, we have to use .NET's assembly loading to load additional assemblies.
        // First calculate all possible cultures that the application might want to load. We do this by
        // starting from the current culture and walking up the graph of parents.
        // At the end of the the walk, we'll have a list of culture names that look like
        // [ "fr-FR", "fr" ]
        while (cultureInfo != null || uiCultureInfo != null)
        {
            if (cultureInfo != null && cultureInfo != CultureInfo.InvariantCulture)
            {
                culturesToLoad.Add(cultureInfo.Name);
            }

            if (uiCultureInfo != null && uiCultureInfo.Name != cultureInfo?.Name && uiCultureInfo != CultureInfo.InvariantCulture)
            {
                culturesToLoad.Add(uiCultureInfo.Name);
            }

            cultureInfo = (cultureInfo?.Parent == cultureInfo || cultureInfo == CultureInfo.InvariantCulture) ? null : cultureInfo?.Parent;
            uiCultureInfo = (uiCultureInfo?.Parent == uiCultureInfo || uiCultureInfo == CultureInfo.InvariantCulture) ? null : uiCultureInfo?.Parent;
        }

        return culturesToLoad.ToList().ToArray();
    }

    private partial class WebAssemblyCultureProviderInterop
    {
        [JSImport("INTERNAL.loadSatelliteAssemblies")]
        public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad);
    }
}


Review comment (suggestion):
This is a costly operation. `culturesToLoad` is a `HashSet<string>`, calling `ToList` allocates and copies elements and then `ToArray` does it again. `HashSet` has it's own `ToArray` inherited from `IEnummerable` (you have to have `System.Linq` reference to use it).

Code after:
    internal static string[] GetCultures(CultureInfo cultureInfo, CultureInfo? uiCultureInfo = null)
    {
        // Once WASM is ready, we have to use .NET's assembly loading to load additional assemblies.
        // First calculate all possible cultures that the application might want to load. We do this by
        // starting from the current culture and walking up the graph of parents.
        // At the end of the the walk, we'll have a list of culture names that look like
        // [ "fr-FR", "fr" ]

        var culturesToLoad = GetCultureHierarchy(cultureInfo);
        if (cultureInfo != uiCultureInfo)
        {
            foreach (var culture in GetCultureHierarchy(uiCultureInfo))
            {
                if (!culturesToLoad.Contains(culture))
                {
                    culturesToLoad = culturesToLoad.Append(culture);
                }
                // If the culture is in the list, we can break because we found the common parent.
                else
                {
                    break;
                }
            }
        }

        return culturesToLoad.ToArray();
    }

    private static IEnumerable<string> GetCultureHierarchy(CultureInfo? culture)
    {
        while (culture != CultureInfo.InvariantCulture && culture != null)
        {
            yield return culture.Name;
            if (culture == culture.Parent)
            {
                break;
            }
            culture = culture.Parent;
        }
    }

    private partial class WebAssemblyCultureProviderInterop
    {
        [JSImport("INTERNAL.loadSatelliteAssemblies")]
        public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad);
    }
}