using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using PCL.Core.Utils.Exts; namespace PCL.Core.App.Configuration; /// /// 配置项。 /// /// 值类型 public class ConfigItem( string key, Func defaultValue, ConfigSource source ) : IConfigScope, ConfigItem { public string Key { get; } = key; public ConfigSource Source { get; } = source; public Type Type => typeof(TValue); private Func? _defaultValueConstructor = defaultValue; private TValue? _defaultValue; private bool _defaultValueHasSet = false; #region 默认值逻辑 private TValue _GetDefaultValue() { if (_defaultValueHasSet) return _defaultValue!; _defaultValue = _defaultValueConstructor!(); _defaultValueHasSet = true; _defaultValueConstructor = null; return _defaultValue; } /// /// 默认值。 /// public TValue DefaultValue => _GetDefaultValue(); public object DefaultValueNoType => DefaultValue ?? default!; #endregion public ConfigItem(string key, TValue defaultValue, ConfigSource source) : this(key, () => defaultValue, source) { } public IEnumerable CheckScope(IReadOnlySet keys) => keys.Contains(Key) ? [Key] : []; #region 值获取和修改 private IConfigProvider _Provider { get => field ??= ConfigService.GetProvider(Source); } = null!; private ConfigValueCache _valueCache = new(); /// /// 指定是否启用缓存。
/// NOTE: 禁用缓存将造成一些功能(如自动监听内容更改)不按预期工作,请仅在真正需要的时候禁用。 ///
public bool EnableCache { get; set { if (!field) _valueCache.InvalidateAll(); field = value; } } = true; /// /// 处理看起来是新的值,并返回是否真的是新的。
/// 只有启用缓存时该方法才会生效,未启用缓存将始终直接返回 。 ///
private bool _ProcessNewCache(TValue newCache, object? argument, bool force = false) { if (!EnableCache) return true; if (!force) { // 判断是否是新值 var existsOld = _valueCache.TryRead(out var oldCache, argument); if (existsOld && EqualityComparer.Default.Equals(oldCache, newCache)) return false; } // 对新缓存值执行准备工作 if (newCache is INotifyPropertyChanged reactive) reactive.PropertyChanged += (_, _) => OnContentChanged(); else if (newCache is INotifyCollectionChanged reactiveCollection) reactiveCollection.CollectionChanged += (_, _) => OnContentChanged(); // 写入缓存 _valueCache.Write(newCache, argument); return true; void OnContentChanged() => SetValue(newCache, argument, bypassCache: true); } /// /// 获取配置值。 /// /// 上下文参数 /// 已设置的配置值或默认值 public TValue GetValue(object? argument = null) { TValue? value = default; // 这个初始化是多余的,但是煞笔巨硬不初始化会报错 var exists = EnableCache && _valueCache.TryRead(out value, argument); var newValue = false; if (!exists) { newValue = true; exists = _Provider.GetValue(Key, out value, argument); } var e = _TriggerEvent(ConfigEvent.Get, argument, value, true); if (e is not null) { if (e.Cancelled) return DefaultValue; if (e.NewValueReplacement is not null) return (TValue)e.NewValueReplacement; } if (!exists) value = DefaultValue; if (newValue) _ProcessNewCache(value!, argument); return value!; } public object GetValueNoType(object? argument = null) { return GetValue(argument) ?? default!; } /// /// 设置配置值。 /// /// 用于设置的值 /// 上下文参数 /// 强制将传入的值视为新值,不检查缓存,仅在 时生效 /// 跳过缓存检查和写入,相当于对本次操作临时将 设为 /// 是否成功设置值,若成功则为 true public bool SetValue(TValue value, object? argument = null, bool forceNewValue = false, bool bypassCache = false) { var e = _TriggerEvent(ConfigEvent.Set, argument, value, isPreview: true); if (e is not null) { if (e.Cancelled) return false; if (e.NewValueReplacement is not null) value = (TValue)e.NewValueReplacement; } if (bypassCache || _ProcessNewCache(value, argument, forceNewValue)) _Provider.SetValue(Key, value, argument); _TriggerEvent(ConfigEvent.Set, argument, value, e: e, isPreview: false); return true; } public bool SetValueNoType(object value, object? argument = null) { try { return SetValue((TValue)value, argument); } catch (InvalidCastException) { // 兼容龙猫妙妙小代码直接传入 string 值的行为 if (value is string v) return SetValue(v.Convert()!, argument); var msg = $"Value convert failed (required: {Type.FullName}, provided: {value.GetType().FullName})"; throw new InvalidCastException(msg); } } public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null) { return SetValue(DefaultValue, argument, forceNewValue ?? IsDefault(argument)); } public bool Reset(object? argument = null) { var e = _TriggerEvent(ConfigEvent.Reset, argument, null, isPreview: true); if (e is { Cancelled: true }) return false; _Provider.Delete(Key, argument); if (EnableCache) _valueCache.Invalidate(argument); _TriggerEvent(ConfigEvent.Reset, argument, DefaultValueNoType, isPreview: false); return true; } public bool IsDefault(object? argument = null) { var result = !_Provider.Exists(Key, argument); var e = _TriggerEvent(ConfigEvent.CheckDefault, argument, result); if (e is { NewValueReplacement: not null }) result = (bool)e.NewValueReplacement; return result; } #endregion #region 事件处理 private readonly HashSet _observers = []; private readonly HashSet _previewObservers = []; public void Observe(ConfigObserver observer) { if (observer.IsPreview) _previewObservers.Add(observer); else _observers.Add(observer); } public bool Unobserve(ConfigObserver observer) => observer.IsPreview ? _previewObservers.Remove(observer) : _observers.Remove(observer); // 获取值,若未设置则返回 null private object? _GetValueOrNull(object? argument) { var exists = _Provider.GetValue(Key, out var value, argument); return exists ? value : null; } public ConfigEventArgs? TriggerEvent( ConfigEvent trigger, object? argument, bool bypassOldValue = false, bool fillNewValue = false) { return _TriggerEvent(trigger, argument, null, bypassOldValue, fillNewValue); } private ConfigEventArgs? _TriggerEvent( ConfigEvent trigger, object? argument, object? newValue, bool bypassOldValue = false, bool fillNewValue = false, ConfigEventArgs? e = null, bool? isPreview = null) { var replaceNewValue = false; foreach (var observer in ( from observer in (isPreview is { } p ? (p ? _previewObservers : _observers) : _previewObservers.Concat(_observers)) let logic = (int)observer.Event & (int)trigger where logic > 0 select observer )) { if (e is null) { if (isPreview == false && !bypassOldValue) bypassOldValue = true; var currentValue = (fillNewValue || !bypassOldValue) ? _GetValueOrNull(argument) : null; if (newValue is null && fillNewValue) newValue = currentValue ?? DefaultValue; e = new ConfigEventArgs(this, trigger, argument, bypassOldValue ? null : currentValue, newValue); } observer.Handler(e); // 对 preview 的特殊处理 if (observer.IsPreview) { if (e.NewValueReplacement is not null) replaceNewValue = true; // 记录替换操作 if (e.Cancelled) return e; } // 防止非 preview 事件传递替换值 else if (!replaceNewValue && e.NewValueReplacement is not null) e.NewValueReplacement = null; } // 防止非 preview 事件传递取消状态 if (e is { Cancelled: true }) e.Cancelled = false; return e; } #endregion } /// /// 的非泛型方法抽象层,用于手动解决巨硬 /// 2025 年仍未支持的极其先进的隐式去泛型化。 /// // ReSharper disable once InconsistentNaming public interface ConfigItem { /// /// 配置键。 /// public string Key { get; } /// /// 配置来源。 /// public ConfigSource Source { get; } /// /// 配置的 CLR 类型。 /// public Type Type { get; } /// /// 传入事件观察器以观察事件。 /// public void Observe(ConfigObserver observer); /// /// 取消观察事件。 /// public bool Unobserve(ConfigObserver observer); /// /// 触发配置项事件。 /// /// 触发事件 /// 上下文参数 /// 若为 true 则向事件参数的旧值传递 null,否则传递当前值 /// 若为 true,当新值为 null 时将传递当前值或默认值 /// public ConfigEventArgs? TriggerEvent( ConfigEvent trigger, object? argument, bool bypassOldValue = false, bool fillNewValue = false ); /// /// 传入事件类型与处理委托以观察事件。 /// public ConfigObserver Observe(ConfigEvent trigger, ConfigEventHandler handler, bool isPreview = false) { var observer = new ConfigObserver(trigger, handler, isPreview); Observe(observer); return observer; } /// /// 传统的用于兼容的值改变事件。
/// 请尽可能避免使用,而是使用 /// 来声明事件观察,或使用 和 /// 来灵活管理事件。 ///
public event ConfigEventHandler Changed { add => Observe(ConfigEvent.Changed, value); remove => throw new NotSupportedException("Please use Observe() and Unobserve() to access advanced event management"); } /// /// 重置配置值,使其变为未设置状态。 /// /// 上下文参数 /// 是否成功重置值,若成功则为 true public bool Reset(object? argument = null); /// /// 检查配置值是否为默认值 (未设置状态) /// /// 上下文参数 public bool IsDefault(object? argument = null); /// /// 将配置项的值设置为默认值,设置后 将返回 false。 /// /// 上下文参数 /// 强制视为新值,不检查缓存,仅在 时生效 /// 是否成功设置值,若成功则为 true public bool SetDefaultValue(object? argument = null, bool? forceNewValue = null); /// /// 没有泛型的
/// 我们都不想给非引用类型装箱,但是龙猫想。 ///
public object GetValueNoType(object? argument = null); /// /// 没有泛型的
/// 我们都不想给非引用类型装箱,但是龙猫想。 ///
public bool SetValueNoType(object value, object? argument = null); /// /// 没有泛型的
/// 我们都不想给非引用类型装箱,但是龙猫想。 ///
public object DefaultValueNoType { get; } /// /// 是否启用值缓存,默认为 true。设为 false 将清除已存在的缓存。 /// public bool EnableCache { get; set; } }