字段临时缓存包装器

前言

在实际开发中,我们有时候存在一种需求,例如对于某个字段,我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留,最终是否应用这些修改取决于某些条件,比如玩家对游戏设置的修改可能需要玩家明确确认应用修改后才会保存下来,在此之前玩家在游戏界面上的所有修改都是临时的。


本文基于这个需求探索出了一种解决方案——“临时缓存包装器”,通过创建字段或用于存储字段临时数据的数据结构的副本来实现临时缓存,虽然我们同样可以采用直接声明一个同类型的新字段的方式来达到同样的目的,但是这可能会增加冗余代码,且不利于代码的维护,通过包装器来封装临时缓存的通用逻辑,从而与具体业务逻辑进行隔离。


简介:该类的开发目的是用于给指定类型的源字段创建副本并将其作为源字段的临时缓存,通过该包装器对副本进行一系列的修改,调用者可以在任何需要的时候从该包装器中读取到新的修改,也可以通过解包的方法将副本数据同步到源字段。

具体功能:

  • 创建包装器的方式包括二进制包装、JSON包装和自定义创建,分别对应三个静态方法。
  • 解包方式分为二进制解包和JSON解包,分别对应两个实例方法。
  • 1.2版本开始仅提供一个基本属性——缓存字段,确认是否为值类型、缓存字段的引用等属性被弃用。
  • 提供了手动释放包装器的方法,可以在不需要时通知 GC 来释放资源。

其它说明:

  • 二进制包装和解包实际上是采用的二进制序列化和反序列化,JSON包装和解包则采用的JSON序列化和反序列化。
  • 临时包装器适用于引用类型,1.2版本开始将不再支持包装值类型。
  • 释放包装器后,所有实例成员的访问将会触发无效操作的异常。

代码

v1.0
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;

/// <summary>
/// 临时包装器
/// </summary>
/// <typeparam name="T">字段类型</typeparam>
/// <remarks>
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存,避免直接修改源字段。
/// </remarks>
public class TempWrapper<T> : IDisposable
{
    /// <summary>
    /// 是否为值类型
    /// <para>提示:若为true则表示包装字段为值类型,否则为引用类型</para>
    /// </summary>
    public static bool isValueType => _isValueType;

    /// <summary>
    /// 缓存字段
    /// <para>提示:对于值类型而言,该属性涉及拷贝</para>
    /// </summary>
    public T value
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return _value;
        }
        set
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            _value = value;
        }
    }

    /// <summary>
    /// 获取引用
    /// <para>提示:对于值类型而言,该属性直接返回引用从而避免拷贝</para>
    /// </summary>
    public ref T refrence
    {
        get
        {
            if (_isDisposed) throw new InvalidOperationException("The wrapper is disposed.");
            return ref _value;
        }
    }

    /// <summary>
    /// 是否已经释放
    /// </summary>
    public bool isDisposed => _isDisposed;

    static readonly bool _isValueType = typeof(T).IsValueType;
    static readonly bool _isDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));
    static readonly object _key = new object();
    T _value;
    bool _isDisposed;

    TempWrapper() { }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化生成字段副本</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public static TempWrapper<T> WrapByBinary(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    using (MemoryStream ms = new MemoryStream())
                    {
                        IFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(ms, value);
                        ms.Seek(0, SeekOrigin.Begin);
                        wrapper._value = (T)formatter.Deserialize(ms);
                    }
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }

        }
    }

    /// <summary>
    /// 包装指定字段并返回包装类
    /// <para>提示:采用JSON序列化和反序列化生成字段副本</para>
    /// </summary>
    /// <param name="value">待包装字段的引用</param>
    public static TempWrapper<T> WrapByJson(ref T value)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>();

                if (_isValueType) wrapper._value = value;
                else
                {
                    string jsonStr = JsonConvert.SerializeObject(value);
                    wrapper._value = JsonConvert.DeserializeObject<T>(jsonStr);
                }

                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 包装生成器所生成的字段并返回包装类
    /// </summary>
    /// <param name="creator">生成器</param>
    public static TempWrapper<T> WrapByCustom(Func<T> creator)
    {
        lock (_key)
        {
            try
            {
                TempWrapper<T> wrapper = new TempWrapper<T>() { _value = creator() };
                return wrapper;
            }
            catch (Exception e)
            {
                throw new InvalidOperationException("Failed to wrap.", e);
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// </summary>
    /// <remarks>
    /// <para>提示:采用二进制序列化和反序列化解包</para>
    /// <para>提示:该方法仅可用于被 <c>Serializable</c> 标记的字段类型</para>
    /// </remarks>
    public void UnWrapByBinary(ref T value)
    {
        if (_isValueType) value = _value;
        else
        {
            lock (_key)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(ms, _value);
                    ms.Seek(0, SeekOrigin.Begin);
                    value = (T)formatter.Deserialize(ms);
                }
            }
        }
    }

    /// <summary>
    /// 解包包装器并赋值给指定的字段
    /// <para>提示:采用JSON序列化和反序列化解包</para>
    /// </summary>
    public void UnwrapByJson(ref T value)
    {
        if (_isValueType) value = _value;
        else
        {
            lock (_key)
            {
                string jsonStr = JsonConvert.SerializeObject(_value);
                value = JsonConvert.DeserializeObject<T>(jsonStr);
            }
        }
    }

    /// <summary>
    /// 释放包装器所包装的字段
    /// <para>提示:当所包装字段实现了IDisposable接口时该方法才有效</para>
    /// </summary>
    public void Dispose()
    {
        if (_isDisposed) return;

        DoDispose(true);
        GC.SuppressFinalize(this);
    }

    void DoDispose(bool disposing)
    {
        if (_isDisposed) return;

        _isDisposed = true;
        if (disposing && _isDisposable && _value is IDisposable ds)
            ds.Dispose();
    }

    ~TempWra
posted @ 2024-09-30 11:49  我与岁月的森林  阅读(0)  评论(0)    收藏  举报  来源