| File: System\ComponentModel\Design\Serialization\ComponentCache.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms.Design\src\System.Windows.Forms.Design.csproj (System.Windows.Forms.Design) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CodeDom; namespace System.ComponentModel.Design.Serialization; /// <summary> /// This class is used to cache serialized properties and events of components to speed-up Design to Code view switches /// </summary> internal sealed partial class ComponentCache : IDisposable { private Dictionary<object, Entry>? _cache; private readonly IDesignerSerializationManager _serManager; internal ComponentCache(IDesignerSerializationManager manager) { _serManager = manager; if (manager.GetService(typeof(IComponentChangeService)) is IComponentChangeService cs) { cs.ComponentChanging += OnComponentChanging; cs.ComponentChanged += OnComponentChanged; cs.ComponentRemoving += OnComponentRemove; cs.ComponentRemoved += OnComponentRemove; cs.ComponentRename += OnComponentRename; } if (manager.TryGetService(out DesignerOptionService? options)) { PropertyDescriptor? componentCacheProp = options.Options.Properties["UseOptimizedCodeGeneration"]; object? optionValue = componentCacheProp?.GetValue(null); if (optionValue is bool boolValue) { Enabled = boolValue; } } } internal bool Enabled { get; } = true; /// <summary> /// Access serialized Properties and events for the given component /// </summary> [DisallowNull] internal Entry? this[object component] { get { ArgumentNullException.ThrowIfNull(component); return Enabled && _cache is not null && _cache.TryGetValue(component, out Entry? result) && result.Valid ? result : null; } set { if (Enabled) { _cache ??= []; } // it's a 1:1 relationship so we can go back from entry to component // (if it's not setup yet.. which should not happen, // see ComponentCodeDomSerializer.cs::Serialize for more info) if (_cache is not null && component is IComponent) { value.Component ??= component; _cache[component] = value; } } } internal Entry? GetEntryAll(object component) { if (_cache is not null && _cache.TryGetValue(component, out Entry? result)) { return result; } return null; } internal bool ContainsLocalName(string name) { if (_cache is null) { return false; } foreach (KeyValuePair<object, Entry> kvp in _cache) { List<string>? localNames = kvp.Value.LocalNames; if (localNames is not null && localNames.Contains(name)) { return true; } } return false; } public void Dispose() { if (_serManager.TryGetService(out IComponentChangeService? cs)) { cs.ComponentChanging -= OnComponentChanging; cs.ComponentChanged -= OnComponentChanged; cs.ComponentRemoving -= OnComponentRemove; cs.ComponentRemoved -= OnComponentRemove; cs.ComponentRename -= OnComponentRename; } } private void OnComponentRename(object? source, ComponentRenameEventArgs? args) { // we might have a symbolic rename that has side effects beyond our control, // so we don't have a choice but to clear the whole cache when a component gets renamed... _cache?.Clear(); } private void OnComponentChanging(object? source, ComponentChangingEventArgs ce) { if (_cache is not null) { if (ce.Component is not null) { RemoveEntry(ce.Component); if (ce.Component is not IComponent) { if (_serManager.TryGetService(out IReferenceService? rs)) { IComponent? owningComp = rs.GetComponent(ce.Component); if (owningComp is not null) { RemoveEntry(owningComp); } else { // Hmm. We were notified about an object change, but were unable to relate it back to a // component we know about. In this situation, we have no option but to clear the whole // cache, since we don't want serialization to miss something. _cache.Clear(); } } } } else { _cache.Clear(); } } } private void OnComponentChanged(object? source, ComponentChangedEventArgs ce) { if (_cache is not null) { if (ce.Component is not null) { RemoveEntry(ce.Component); if (ce.Component is not IComponent) { if (_serManager.TryGetService(out IReferenceService? rs)) { IComponent? owningComp = rs.GetComponent(ce.Component); if (owningComp is not null) { RemoveEntry(owningComp); } else { // Hmm. We were notified about an object change, but were unable to relate it back to a // component we know about. In this situation, we have no option but to clear the whole // cache, since we don't want serialization to miss something. _cache.Clear(); } } } } else { _cache.Clear(); } } } private void OnComponentRemove(object? source, ComponentEventArgs ce) { if (_cache is not null) { if (ce.Component is not null and not IExtenderProvider) { RemoveEntry(ce.Component); } else { _cache.Clear(); } } } /// <summary> /// Helper to remove an entry from the cache. /// </summary> internal void RemoveEntry(object component) { if (_cache is not null && _cache.TryGetValue(component, out Entry? entry)) { if (entry.Tracking) { _cache.Clear(); return; } _cache.Remove(component); // Clear its dependencies, if any if (entry.Dependencies is not null) { foreach (object? parent in entry.Dependencies) { RemoveEntry(parent); } } } } // A single cache entry internal sealed class Entry { private List<ResourceEntry>? _resources; private List<ResourceEntry>? _metadata; internal Entry() { Valid = true; } public object? Component; // pointer back to the component that generated this entry public CodeStatementCollection Statements = []; public ICollection<ResourceEntry>? Metadata => _metadata; public ICollection<ResourceEntry>? Resources => _resources; public List<object>? Dependencies { get; private set; } internal List<string>? LocalNames { get; private set; } internal bool Valid { get; set; } internal bool Tracking { get; set; } internal void AddLocalName(string name) { LocalNames ??= []; LocalNames.Add(name); } public void AddDependency(object dep) { Dependencies ??= []; if (!Dependencies.Contains(dep)) { Dependencies.Add(dep); } } public void AddMetadata(ResourceEntry re) { _metadata ??= []; _metadata.Add(re); } public void AddResource(ResourceEntry re) { _resources ??= []; _resources.Add(re); } } }