diff --git a/src/Umbraco.Core/Composing/TypeFinderConfig.cs b/src/Umbraco.Core/Composing/TypeFinderConfig.cs index 2fd9283500..efba61ccb4 100644 --- a/src/Umbraco.Core/Composing/TypeFinderConfig.cs +++ b/src/Umbraco.Core/Composing/TypeFinderConfig.cs @@ -25,8 +25,8 @@ public class TypeFinderConfig : ITypeFinderConfig var s = _settings.AssembliesAcceptingLoadExceptions; return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s) - ? Array.Empty() - : s.Split(',').Select(x => x.Trim()).ToArray(); + ? [] + : s.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/src/Umbraco.Core/Extensions/StringExtensions.cs b/src/Umbraco.Core/Extensions/StringExtensions.cs index 7a3d90b205..7f3cbbd66b 100644 --- a/src/Umbraco.Core/Extensions/StringExtensions.cs +++ b/src/Umbraco.Core/Extensions/StringExtensions.cs @@ -509,8 +509,7 @@ public static class StringExtensions var convertToHex = input.ConvertToHex(); var hexLength = convertToHex.Length < 32 ? convertToHex.Length : 32; var hex = convertToHex[..hexLength].PadLeft(32, '0'); - Guid output = Guid.Empty; - return Guid.TryParse(hex, out output) ? output : Guid.Empty; + return Guid.TryParse(hex, out Guid output) ? output : Guid.Empty; } /// diff --git a/src/Umbraco.Core/Factories/UserSettingsFactory.cs b/src/Umbraco.Core/Factories/UserSettingsFactory.cs index c006fe0043..99da578eaa 100644 --- a/src/Umbraco.Core/Factories/UserSettingsFactory.cs +++ b/src/Umbraco.Core/Factories/UserSettingsFactory.cs @@ -36,7 +36,6 @@ public class UserSettingsFactory : IUserSettingsFactory private IEnumerable CreateConsentLevelModels() => Enum.GetValues() - .ToList() .Select(level => new ConsentLevelModel { Level = level, diff --git a/src/Umbraco.Core/Logging/LoggingConfiguration.cs b/src/Umbraco.Core/Logging/LoggingConfiguration.cs index b6d6893482..d21d56dfea 100644 --- a/src/Umbraco.Core/Logging/LoggingConfiguration.cs +++ b/src/Umbraco.Core/Logging/LoggingConfiguration.cs @@ -58,8 +58,7 @@ public class LoggingConfiguration : ILoggingConfiguration public string LogFileNameFormat { get; } /// - public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) + public string[] GetLogFileNameFormatArguments() => _logFileNameFormatArguments.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(GetValue) .ToArray(); diff --git a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs index b3bc74e54c..c408d40100 100644 --- a/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs +++ b/src/Umbraco.Core/Models/Blocks/BlockEditorDataConverter.cs @@ -71,12 +71,12 @@ public abstract class BlockEditorDataConverter // this method is only meant to have any effect when migrating block editor values // from the original format to the new, variant enabled format - private void AmendExpose(TValue value) - => value.Expose = value.ContentData.Select(cd => new BlockItemVariation(cd.Key, null, null)).ToList(); + private static void AmendExpose(TValue value) + => value.Expose = value.ContentData.ConvertAll(cd => new BlockItemVariation(cd.Key, null, null)); // this method is only meant to have any effect when migrating block editor values // from the original format to the new, variant enabled format - private bool ConvertOriginalBlockFormat(List blockItemDatas) + private static bool ConvertOriginalBlockFormat(List blockItemDatas) { var converted = false; foreach (BlockItemData blockItemData in blockItemDatas) diff --git a/src/Umbraco.Core/Routing/DomainUtilities.cs b/src/Umbraco.Core/Routing/DomainUtilities.cs index 2d9be885a3..03ddd438d3 100644 --- a/src/Umbraco.Core/Routing/DomainUtilities.cs +++ b/src/Umbraco.Core/Routing/DomainUtilities.cs @@ -256,14 +256,14 @@ namespace Umbraco.Cms.Core.Routing // if a culture is specified, then try to get domains for that culture // (else cultureDomains will be null) // do NOT specify a default culture, else it would pick those domains - IReadOnlyCollection? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); + IReadOnlyList? cultureDomains = SelectByCulture(domainsAndUris, culture, defaultCulture: null); IReadOnlyCollection considerForBaseDomains = domainsAndUris; if (cultureDomains != null) { if (cultureDomains.Count == 1) { // only 1, return - return cultureDomains.First(); + return cultureDomains[0]; } // else restrict to those domains, for base lookup @@ -272,11 +272,11 @@ namespace Umbraco.Cms.Core.Routing // look for domains that would be the base of the uri // we need to order so example.com/foo matches before example.com/ - IReadOnlyCollection baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToList(), uri, culture); + List baseDomains = SelectByBase(considerForBaseDomains.OrderByDescending(d => d.Uri.ToString()).ToArray(), uri, culture); if (baseDomains.Count > 0) { // found, return - return baseDomains.First(); + return baseDomains[0]; } // if nothing works, then try to run the filter to select a domain @@ -296,7 +296,7 @@ namespace Umbraco.Cms.Core.Routing private static bool MatchesCulture(DomainAndUri domain, string? culture) => culture == null || domain.Culture.InvariantEquals(culture); - private static IReadOnlyCollection SelectByBase(IReadOnlyCollection domainsAndUris, Uri uri, string? culture) + private static List SelectByBase(DomainAndUri[] domainsAndUris, Uri uri, string? culture) { // look for domains that would be the base of the uri // ie current is www.example.com/foo/bar, look for domain www.example.com @@ -314,7 +314,7 @@ namespace Umbraco.Cms.Core.Routing return baseDomains; } - private static IReadOnlyCollection? SelectByCulture(IReadOnlyCollection domainsAndUris, string? culture, string? defaultCulture) + private static List? SelectByCulture(DomainAndUri[] domainsAndUris, string? culture, string? defaultCulture) { // we try our best to match cultures, but may end with a bogus domain if (culture is not null) @@ -434,13 +434,18 @@ namespace Umbraco.Cms.Core.Routing private static Domain? FindDomainInPath(IEnumerable? domains, string path, int? rootNodeId, bool isWildcard) { + if (domains is null) + { + return null; + } + var stopNodeId = rootNodeId ?? -1; return path.Split(Constants.CharArrays.Comma) .Reverse() .Select(s => int.Parse(s, CultureInfo.InvariantCulture)) .TakeWhile(id => id != stopNodeId) - .Select(id => domains?.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard)) + .Select(id => domains.FirstOrDefault(d => d.ContentId == id && d.IsWildcard == isWildcard)) .FirstOrDefault(domain => domain is not null); } diff --git a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs index d7562a76b9..bb849c61e7 100644 --- a/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs +++ b/src/Umbraco.Core/Services/Navigation/ContentNavigationServiceBase.cs @@ -17,8 +17,8 @@ internal abstract class ContentNavigationServiceBase> _contentTypeAliasToKeyMap; private ConcurrentDictionary _navigationStructure = new(); private ConcurrentDictionary _recycleBinNavigationStructure = new(); - private IList _roots = new List(); - private IList _recycleBinRoots = new List(); + private HashSet _roots = []; + private HashSet _recycleBinRoots = []; protected ContentNavigationServiceBase(ICoreScopeProvider coreScopeProvider, INavigationRepository navigationRepository, TContentTypeService typeService) { @@ -321,7 +321,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid childKey, out Guid? parentKey) + private static bool TryGetParentKeyFromStructure(ConcurrentDictionary structure, Guid childKey, out Guid? parentKey) { if (structure.TryGetValue(childKey, out NavigationNode? childNode)) { @@ -335,25 +335,32 @@ internal abstract class ContentNavigationServiceBase input, + HashSet input, out IEnumerable rootKeys, Guid? contentTypeKey = null) { - // Apply contentTypeKey filter - IEnumerable filteredKeys = contentTypeKey.HasValue - ? input.Where(key => _navigationStructure[key].ContentTypeKey == contentTypeKey.Value) - : input; + var keysWithSortOrder = new List<(Guid Key, int SortOrder)>(input.Count); + foreach (Guid key in input) + { + NavigationNode navigationNode = _navigationStructure[key]; + + // Apply contentTypeKey filter + if (contentTypeKey.HasValue && navigationNode.ContentTypeKey != contentTypeKey.Value) + { + continue; + } + + keysWithSortOrder.Add((key, navigationNode.SortOrder)); + } - // TODO can we make this more efficient? // Sort by SortOrder - rootKeys = filteredKeys - .OrderBy(key => _navigationStructure[key].SortOrder) - .ToList(); + keysWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); + rootKeys = keysWithSortOrder.ConvertAll(keyWithSortOrder => keyWithSortOrder.Key); return true; } - private bool TryGetChildrenKeysFromStructure( + private static bool TryGetChildrenKeysFromStructure( ConcurrentDictionary structure, Guid parentKey, out IEnumerable childrenKeys, @@ -367,12 +374,12 @@ internal abstract class ContentNavigationServiceBase structure, Guid parentKey, out IEnumerable descendantsKeys, @@ -393,7 +400,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid childKey, out IEnumerable ancestorsKeys, @@ -421,7 +428,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid key, out IEnumerable siblingsKeys, @@ -463,14 +470,14 @@ internal abstract class ContentNavigationServiceBase structure, NavigationNode node, List descendants, Guid? contentTypeKey = null) { // Get all children regardless of contentType - var childrenKeys = GetOrderedChildren(node, structure).ToList(); + IReadOnlyList childrenKeys = GetOrderedChildren(node, structure); foreach (Guid childKey in childrenKeys) { // Apply contentTypeKey filter @@ -487,7 +494,7 @@ internal abstract class ContentNavigationServiceBase structure, Guid key, out NavigationNode? nodeToRemove) + private static bool TryRemoveNodeFromParentInStructure(ConcurrentDictionary structure, Guid key, out NavigationNode? nodeToRemove) { if (structure.TryGetValue(key, out nodeToRemove) is false) { @@ -507,7 +514,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _navigationStructure); foreach (Guid childKey in childrenKeys) { @@ -530,7 +537,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure); foreach (Guid childKey in childrenKeys) { if (_recycleBinNavigationStructure.TryGetValue(childKey, out NavigationNode? childNode) is false) @@ -551,7 +558,7 @@ internal abstract class ContentNavigationServiceBase childrenKeys = GetOrderedChildren(node, _recycleBinNavigationStructure); foreach (Guid childKey in childrenKeys) { @@ -570,24 +577,35 @@ internal abstract class ContentNavigationServiceBase GetOrderedChildren( + private static IReadOnlyList GetOrderedChildren( NavigationNode node, ConcurrentDictionary structure, Guid? contentTypeKey = null) { - IEnumerable children = node - .Children - .Where(structure.ContainsKey); - - // Apply contentTypeKey filter - if (contentTypeKey.HasValue) + if (node.Children.Count < 1) { - children = children.Where(childKey => structure[childKey].ContentTypeKey == contentTypeKey.Value); + return []; } - return children - .OrderBy(childKey => structure[childKey].SortOrder) - .ToList(); + var childrenWithSortOrder = new List<(Guid ChildNodeKey, int SortOrder)>(node.Children.Count); + foreach (Guid childNodeKey in node.Children) + { + if (!structure.TryGetValue(childNodeKey, out NavigationNode? childNode)) + { + continue; + } + + // Apply contentTypeKey filter + if (contentTypeKey.HasValue && childNode.ContentTypeKey != contentTypeKey.Value) + { + continue; + } + + childrenWithSortOrder.Add((childNodeKey, childNode.SortOrder)); + } + + childrenWithSortOrder.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); + return childrenWithSortOrder.ConvertAll(childWithSortOrder => childWithSortOrder.ChildNodeKey); } private bool TryGetContentTypeKey(string contentTypeAlias, out Guid? contentTypeKey) @@ -613,7 +631,7 @@ internal abstract class ContentNavigationServiceBase nodesStructure, IList roots, IEnumerable entities) + private static void BuildNavigationDictionary(ConcurrentDictionary nodesStructure, HashSet roots, IEnumerable entities) { var entityList = entities.ToList(); var idToKeyMap = entityList.ToDictionary(x => x.Id, x => x.Key); diff --git a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs index f3d14a6fe2..85ce813907 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetHelper.cs @@ -39,8 +39,7 @@ public class StylesheetHelper // Only match first selector when chained together Styles = string.Join( Environment.NewLine, - match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None) - .Select(x => x.Trim()).ToArray()), + match.Groups["Styles"].Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.TrimEntries)), }); } } diff --git a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs index 4b726f34ef..032901c13e 100644 --- a/src/Umbraco.Core/Strings/Css/StylesheetRule.cs +++ b/src/Umbraco.Core/Strings/Css/StylesheetRule.cs @@ -30,13 +30,13 @@ public class StylesheetRule // instead of using string interpolation (for increased performance) foreach (var style in Styles?.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries) ?? - Array.Empty()) + []) { - sb.Append("\t").Append(style.StripNewLines().Trim()).Append(";").Append(Environment.NewLine); + sb.Append('\t').Append(style.StripNewLines().Trim()).Append(';').Append(Environment.NewLine); } } - sb.Append("}"); + sb.Append('}'); return sb.ToString(); } diff --git a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs index 0b3e73fe73..660af70f05 100644 --- a/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs +++ b/src/Umbraco.Web.Website/Models/RegisterModelBuilder.cs @@ -68,7 +68,7 @@ public class RegisterModelBuilder : MemberModelBuilderBase UsernameIsEmail = _usernameIsEmail, MemberProperties = _lookupProperties ? GetMemberPropertiesViewModel(memberType) - : Enumerable.Empty().ToList(), + : [], AutomaticLogIn = _automaticLogIn }; return model;