| File: System\Windows\Forms\PropertyStore.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; using System.Runtime.CompilerServices; namespace System.Windows.Forms; /// <summary> /// Efficient property store that avoids boxing for common value types. /// </summary> /// <remarks> /// <para> /// This class discourages storing <see langword="null"/> values. /// </para> /// </remarks> internal sealed class PropertyStore { private static int s_currentKey; private readonly Dictionary<int, Value> _values = []; /// <summary> /// Returns <see langword="true"/> if the current key has a value in the <see cref="PropertyStore"/>. /// </summary> public bool ContainsKey(int key) => _values.ContainsKey(key); /// <summary> /// Creates a new key for this property store. /// </summary> public static int CreateKey() => s_currentKey++; /// <summary> /// Removes the given key from the store. /// </summary> public void RemoveValue(int key) => _values.Remove(key); /// <summary> /// Gets the current value for the given key, or the <paramref name="defaultValue"/> if the key is not found. /// </summary> public T? GetValueOrDefault<T>(int key, T? defaultValue = default) { if (_values.TryGetValue(key, out Value foundValue)) { return foundValue.Type == typeof(StrongBox<T>) ? foundValue.GetValue<StrongBox<T>>().Value : foundValue.GetValue<T>(); } return defaultValue; } /// <summary> /// Gets the current value for the given key, or the <paramref name="defaultValue"/> if the key is not found. /// If the stored value is <see langword="null"/>, it will return <see langword="null"/>. /// </summary> public T? GetValueOrDefaultAllowNull<T>(int key, T? defaultValue = default) where T : class? { if (_values.TryGetValue(key, out Value foundValue)) { return foundValue.Type is null ? null : foundValue.GetValue<T>(); } return defaultValue; } /// <summary> /// Gets the current string value for the given key, or <see cref="string.Empty"/> if the key is not found. /// </summary> public string GetStringOrEmptyString(int key) { if (_values.TryGetValue(key, out Value foundValue)) { return foundValue.GetValue<string>(); } return string.Empty; } /// <summary> /// Tries to get the value for the given key. Use <see cref="TryGetValueOrNull{T}(int, out T)"/> if /// <see langword="null"/> values are allowed. /// </summary> /// <inheritdoc cref="TryGetValueOrNull{T}(int, out T)"/> public bool TryGetValue<T>(int key, [NotNullWhen(true)] out T? value) { if (_values.TryGetValue(key, out Value foundValue)) { value = foundValue.Type == typeof(StrongBox<T>) ? foundValue.GetValue<StrongBox<T>>().Value : foundValue.GetValue<T>(); return value is not null; } value = default; return false; } /// <summary> /// Tries to get the value for the given key, allowing explicitly set <see langword="null"/> values. Prefer /// <see cref="TryGetValue{T}(int, out T)"/> if <see langword="null"/> values are not allowed. /// </summary> /// <param name="value"> /// <para> /// The value if found, or <see langword="default"/> if not found. /// </para> /// </param> /// <returns><see langword="true"/> if the value was found.</returns> public bool TryGetValueOrNull<T>(int key, out T? value) where T : class { if (_values.TryGetValue(key, out Value foundValue)) { value = foundValue.Type is null ? null : foundValue.GetValue<T>(); return true; } value = default; return false; } /// <summary> /// Setting <see langword="null"/> or <see cref="string.Empty"/> will clear the value. /// </summary> /// <returns> /// <see langword="true"/> if the stored value was changed. /// </returns> public bool AddOrRemoveString(int key, string? value) { bool found = TryGetValue(key, out string? previous); bool changed = false; if (string.IsNullOrEmpty(value)) { if (found) { _values.Remove(key); changed = true; } } else if (previous != value) { _values[key] = new(value); changed = true; } return changed; } /// <summary> /// Sets the given value or clears it from the store if the value equals <paramref name="defaultValue"/>. /// </summary> /// <remarks> /// <para> /// Always explicitly set <paramref name="defaultValue"/> when using enums for clarity. /// </para> /// </remarks> /// <returns>The previous value if it was set, or <paramref name="defaultValue"/>.</returns> public T? AddOrRemoveValue<T>(int key, T? value, T? defaultValue = default) { bool found = _values.TryGetValue(key, out Value foundValue); bool isDefault = (value is null && defaultValue is null) || (value is not null && value.Equals(defaultValue)); bool isStrongBox = foundValue.Type == typeof(StrongBox<T>); // The previous should be whatever we found or what was specified as the default. T? previous = found ? isStrongBox ? foundValue.GetValue<StrongBox<T>>().Value : foundValue.Type is null ? default : foundValue.GetValue<T>() : defaultValue; if (isDefault) { // Equivalent to default, remove if we found it. if (found) { _values.Remove(key); } } else if (!found || !ReferenceEquals(value, previous)) { // If it wasn't found or it isn't the same instance we need set it. AddValue(key, value); } return previous; } /// <summary> /// Adds the given value to the store. /// </summary> [return: NotNullIfNotNull(nameof(value))] public T AddValue<T>(int key, T value) { // For value types that are larger than 8 bytes, we attempt to update the existing value // to avoid another boxing allocation. if (typeof(T) == typeof(Padding)) { AddOrUpdate(key, Unsafe.As<T, Padding>(ref value)); } else if (typeof(T) == typeof(Rectangle)) { AddOrUpdate(key, Unsafe.As<T, Rectangle>(ref value)); } else { _values[key] = Value.Create(value); } return value; } private unsafe void AddOrUpdate<T>(int key, T value) where T : unmanaged { // Should only call this from SetValue<T> for value types that are larger than 8 bytes. Debug.Assert(sizeof(T) > 8); if (_values.TryGetValue(key, out Value foundValue) && foundValue.Type == typeof(StrongBox<T>)) { StrongBox<T> storedValue = foundValue.GetValue<StrongBox<T>>(); storedValue.Value = value; } else { _values[key] = Value.Create(new StrongBox<T>(value)); } } }