diff --git a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs index 1c186eab3a7d8..3d5b6711644b2 100644 --- a/src/Features/Core/Portable/CodeFixes/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/CodeFixService.cs @@ -40,12 +40,11 @@ internal partial class CodeFixService : ICodeFixService private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableArray> _fixers; - private readonly Dictionary>> _fixersPerLanguageMap; + private readonly ImmutableDictionary>> _fixersPerLanguageMap; private readonly ConditionalWeakTable, ImmutableDictionary>> _projectFixersMap = new(); // Shared by project fixers and workspace fixers. - private readonly Lazy> _lazyFixerToMetadataMap; private readonly ConditionalWeakTable _analyzerReferenceToFixersMap = new(); private readonly ConditionalWeakTable.CreateValueCallback _createProjectCodeFixProvider = r => new ProjectCodeFixProvider(r); private readonly ImmutableDictionary>> _configurationProvidersMap; @@ -56,6 +55,7 @@ internal partial class CodeFixService : ICodeFixService private ImmutableDictionary> _fixerToFixableIdsMap = ImmutableDictionary>.Empty; private ImmutableDictionary _fixAllProviderMap = ImmutableDictionary.Empty; + private ImmutableDictionary _fixerToMetadataMap = ImmutableDictionary.Empty; [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -71,13 +71,9 @@ public CodeFixService( _fixers = fixers.ToImmutableArray(); _fixersPerLanguageMap = _fixers.ToPerLanguageMapWithMultipleLanguages(); - _lazyFixerToMetadataMap = new(() => fixers.Where(service => service.IsValueCreated).ToImmutableDictionary(service => service.Value, service => service.Metadata)); - _configurationProvidersMap = GetConfigurationProvidersPerLanguageMap(configurationProviders); } - private ImmutableDictionary FixerToMetadataMap => _lazyFixerToMetadataMap.Value; - public async Task GetMostSevereFixableDiagnosticAsync( Document document, TextSpan range, CancellationToken cancellationToken) { @@ -433,7 +429,7 @@ await AppendFixesOrConfigurationsAsync( getFixes: dxs => { var fixerName = fixer.GetType().Name; - FixerToMetadataMap.TryGetValue(fixer, out var fixerMetadata); + var fixerMetadata = TryGetMetadata(fixer); using (addOperationScope(fixerName)) using (RoslynEventSource.LogInformationalBlock(FunctionId.CodeFixes_GetCodeFixesAsync, fixerName, cancellationToken)) @@ -468,6 +464,24 @@ await AppendFixesOrConfigurationsAsync( } } + private CodeChangeProviderMetadata? TryGetMetadata(CodeFixProvider fixer) + { + return ImmutableInterlocked.GetOrAdd( + ref _fixerToMetadataMap, + fixer, + static (fixer, fixers) => + { + foreach (var lazy in fixers) + { + if (lazy.IsValueCreated && lazy.Value == fixer) + return lazy.Metadata; + } + + return null; + }, + _fixers); + } + private static async Task> GetCodeFixesAsync( Document document, TextSpan span, CodeFixProvider fixer, CodeChangeProviderMetadata? fixerMetadata, CodeActionOptions options, ImmutableArray diagnostics, @@ -611,7 +625,7 @@ await diagnosticsWithSameSpan.OrderByDescending(d => d.Severity) } // If the fix provider supports fix all occurrences, then get the corresponding FixAllProviderInfo and fix all context. - var fixAllProviderInfo = extensionManager.PerformFunction(fixer, () => ImmutableInterlocked.GetOrAdd(ref _fixAllProviderMap, fixer, FixAllProviderInfo.Create), defaultValue: null); + var fixAllProviderInfo = extensionManager.PerformFunction(fixer, () => ImmutableInterlocked.GetOrAdd(ref _fixAllProviderMap, fixer, FixAllProviderInfo.Create), defaultValue: null); FixAllState? fixAllState = null; var supportedScopes = ImmutableArray.Empty; @@ -890,7 +904,7 @@ private static ImmutableDictionary GetConfigurationFixProviders(List> languageKindAndFixers) + static ImmutableArray GetConfigurationFixProviders(ImmutableArray> languageKindAndFixers) { using var builderDisposer = ArrayBuilder.GetInstance(out var builder); var orderedLanguageKindAndFixers = ExtensionOrderer.Order(languageKindAndFixers); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs index b67e08139188d..2693c87039496 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ILanguageMetadataExtensions.cs @@ -41,10 +41,10 @@ public static ImmutableDictionary new KeyValuePair>>(kvp.Key, kvp.Value.ToImmutableAndFree())).ToImmutableDictionary(); } - public static Dictionary>> ToPerLanguageMapWithMultipleLanguages(this IEnumerable> services) + public static ImmutableDictionary>> ToPerLanguageMapWithMultipleLanguages(this IEnumerable> services) where TMetadata : ILanguagesMetadata { - var map = new Dictionary>>(); + using var _ = PooledDictionary>>.GetInstance(out var map); foreach (var service in services) { @@ -52,13 +52,13 @@ public static Dictionary>> ToPerLanguag { if (!string.IsNullOrEmpty(language)) { - var list = map.GetOrAdd(language, _ => new List>()); + var list = map.GetOrAdd(language, _ => ArrayBuilder>.GetInstance()); list.Add(service); } } } - return map; + return map.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.ToImmutableAndFree()); } } }