/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Impl/CodeModel/CodeModelProjectCache.cs
240 строк
8 KB
Cyrus Najmabadi
Use implicit object creation expressions
13 сен 2025, 01:48
13 сен 2025, 01:48
4c6a36a
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.LanguageServices.Implementation.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; /// <summary> /// Cache FileCodeModel instances for a given project (we are using WeakReference for now, /// so that we can more or less match the semantics of the former native implementation, which /// offered reference equality until all instances were collected by the GC) /// </summary> internal sealed partial class CodeModelProjectCache { private readonly ProjectId _projectId; private readonly ICodeModelInstanceFactory _codeModelInstanceFactory; private readonly Dictionary<string, CacheEntry> _cache = new(StringComparer.OrdinalIgnoreCase); private readonly object _cacheGate = new(); private EnvDTE.CodeModel? _rootCodeModel; private bool _zombied; internal CodeModelState State { get; } internal CodeModelProjectCache( IThreadingContext threadingContext, ProjectId projectId, ICodeModelInstanceFactory codeModelInstanceFactory, ProjectCodeModelFactory projectFactory, IServiceProvider serviceProvider, Microsoft.CodeAnalysis.Host.LanguageServices languageServices, VisualStudioWorkspace workspace) { State = new CodeModelState(threadingContext, serviceProvider, languageServices, workspace, projectFactory); _projectId = projectId; _codeModelInstanceFactory = codeModelInstanceFactory; } /// <summary> /// Look for an existing instance of FileCodeModel in our cache. /// Return null if there is no active FCM for "fileName". /// </summary> private CacheEntry? GetCacheEntry(string fileName) { lock (_cacheGate) { if (_cache.TryGetValue(fileName, out var cacheEntry)) { return cacheEntry; } } return null; } public ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel> GetOrCreateFileCodeModel(string filePath) { // First try { var cacheEntry = GetCacheEntry(filePath); if (cacheEntry != null) { var comHandle = cacheEntry.Value.ComHandle; if (comHandle != null) { return comHandle.Value; } } } // This ultimately ends up calling GetOrCreateFileCodeModel(fileName, parent) with the correct "parent" object // through the project system. var newFileCodeModel = (EnvDTE80.FileCodeModel2)_codeModelInstanceFactory.TryCreateFileCodeModelThroughProjectSystem(filePath); return new ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>(newFileCodeModel); } public ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>? GetComHandleForFileCodeModel(string filePath) { var cacheEntry = GetCacheEntry(filePath); return cacheEntry?.ComHandle; } public ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel> GetOrCreateFileCodeModel(string filePath, object? parent) { // First try { var cacheEntry = GetCacheEntry(filePath); if (cacheEntry != null) { var comHandle = cacheEntry.Value.ComHandle; if (comHandle != null) { return comHandle.Value; } } } // Check that we know about this file! var solution = State.Workspace.CurrentSolution; var documentId = solution.GetDocumentIdsWithFilePath(filePath).Where(id => id.ProjectId == _projectId).FirstOrDefault(); if (documentId == null || solution.GetDocument(documentId) == null) { // Matches behavior of native (C#) implementation throw Exceptions.ThrowENotImpl(); } // Create object (outside of lock) var newFileCodeModel = FileCodeModel.Create(State, parent, documentId, isSourceGeneratorOutput: false, new TextManagerAdapter()); var newCacheEntry = new CacheEntry(newFileCodeModel); // Second try (object might have been added by another thread at this point!) lock (_cacheGate) { var cacheEntry = GetCacheEntry(filePath); if (cacheEntry != null) { var comHandle = cacheEntry.Value.ComHandle; if (comHandle != null) { return comHandle.Value; } } // Note: Using the indexer here (instead of "Add") is relevant since the old // WeakReference entry is likely still in the cache (with a Null target, of course) _cache[filePath] = newCacheEntry; return newFileCodeModel; } } public EnvDTE.CodeModel GetOrCreateRootCodeModel(EnvDTE.Project parent) { if (_zombied) { Debug.Fail("Cannot access root code model after code model was shutdown!"); throw Exceptions.ThrowEUnexpected(); } _rootCodeModel ??= RootCodeModel.Create(State, parent, _projectId); return _rootCodeModel; } public IEnumerable<ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>> GetFileCodeModelInstances() { var result = new List<ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>>(); lock (_cacheGate) { foreach (var cacheEntry in _cache.Values) { var comHandle = cacheEntry.ComHandle; if (comHandle != null) { result.Add(comHandle.Value); } } } return result; } public void OnProjectClosed() { var instances = GetFileCodeModelInstances(); lock (_cacheGate) { _cache.Clear(); } foreach (var instance in instances) { instance.Object.Shutdown(); } _zombied = true; } public void OnSourceFileRemoved(string fileName) { ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>? comHandle = null; lock (_cacheGate) { if (_cache.TryGetValue(fileName, out var cacheEntry)) { comHandle = cacheEntry.ComHandle; _cache.Remove(fileName); } } comHandle?.Object.Shutdown(); } public void OnSourceFileRenaming(string oldFileName, string newFileName) { ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>? comHandleToRename = null; ComHandle<EnvDTE80.FileCodeModel2, FileCodeModel>? comHandleToShutDown = null; lock (_cacheGate) { if (_cache.TryGetValue(oldFileName, out var cacheEntry)) { comHandleToRename = cacheEntry.ComHandle; _cache.Remove(oldFileName); if (comHandleToRename != null) { // We might already have a code model for this new filename. This can happen if // we were to rename Goo.cs to Goocs, which will call this method, and then rename // it back, which does not call this method. This results in both Goo.cs and Goocs // being in the cache. We could fix that "correctly", but the zombied Goocs code model // is pretty broken, so there's no point in trying to reuse it. if (_cache.TryGetValue(newFileName, out cacheEntry)) { comHandleToShutDown = cacheEntry.ComHandle; } _cache.Add(newFileName, cacheEntry); } } } comHandleToShutDown?.Object.Shutdown(); comHandleToRename?.Object.OnRename(newFileName); } }