深圳罗湖医疗集团网站建设,wordpress哪个模板好用,动漫制作技术专业常识,免费营销型网站建设前言 在实际开发中#xff0c;我们有时候存在一种需求#xff0c;例如对于某个字段#xff0c;我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留#xff0c;最终是否应用这些修改取决于某些条件#xff0c;比如玩家对游戏设置的修改可能需要玩家明确确认应用修…前言 在实际开发中我们有时候存在一种需求例如对于某个字段我们希望在某个明确的保存节点前对字段的修改都仅作为缓存保留最终是否应用这些修改取决于某些条件比如玩家对游戏设置的修改可能需要玩家明确确认应用修改后才会保存下来在此之前玩家在游戏界面上的所有修改都是临时的。 本文基于这个需求探索出了一种解决方案“字段临时缓存包装器”通过创建字段或用于存储字段临时数据的数据结构的副本来实现临时缓存虽然我们同样可以采用直接声明一个副本字段的方式来达到同样的目的但是这可能会增加冗余代码且不利于代码的维护通过包装器来封装临时缓存的通用逻辑与具体业务逻辑隔离。 代码
v1.0
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;/// summary
/// 临时包装器
/// /summary
/// typeparam nameT字段类型/typeparam
/// remarks
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存避免直接修改源字段。
/// /remarks
public class TempWrapperT : IDisposable
{/// summary/// 是否为值类型/// para提示若为true则表示包装字段为值类型否则为引用类型/para/// /summarypublic static bool isValueType _isValueType;/// summary/// 缓存字段/// para提示对于值类型而言该属性涉及拷贝/para/// /summarypublic 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/// /summarypublic ref T refrence{get{if (_isDisposed) throw new InvalidOperationException(The wrapper is disposed.);return ref _value;}}/// summary/// 是否已经释放/// /summarypublic 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 namevalue待包装字段的引用/param/// remarks/// para提示采用二进制序列化和反序列化生成字段副本/para/// para提示该方法仅可用于被 cSerializable/c 标记的字段类型/para/// /remarkspublic static TempWrapperT WrapByBinary(ref T value){lock (_key){try{TempWrapperT wrapper new TempWrapperT();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 namevalue待包装字段的引用/parampublic static TempWrapperT WrapByJson(ref T value){lock (_key){try{TempWrapperT wrapper new TempWrapperT();if (_isValueType) wrapper._value value;else{string jsonStr JsonConvert.SerializeObject(value);wrapper._value JsonConvert.DeserializeObjectT(jsonStr);}return wrapper;}catch (Exception e){throw new InvalidOperationException(Failed to wrap., e);}}}/// summary/// 包装生成器所生成的字段并返回包装类/// /summary/// param namecreator生成器/parampublic static TempWrapperT WrapByCustom(FuncT creator){lock (_key){try{TempWrapperT wrapper new TempWrapperT() { _value creator() };return wrapper;}catch (Exception e){throw new InvalidOperationException(Failed to wrap., e);}}}/// summary/// 解包包装器并赋值给指定的字段/// /summary/// remarks/// para提示采用二进制序列化和反序列化解包/para/// para提示该方法仅可用于被 cSerializable/c 标记的字段类型/para/// /remarkspublic 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/// /summarypublic void UnwrapByJson(ref T value){if (_isValueType) value _value;else{lock (_key){string jsonStr JsonConvert.SerializeObject(_value);value JsonConvert.DeserializeObjectT(jsonStr);}}}/// summary/// 释放包装器所包装的字段/// para提示当所包装字段实现了IDisposable接口时该方法才有效/para/// /summarypublic 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();}~TempWrapper(){DoDispose(false);}
}
v1.1
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;/// summary
/// 临时包装器
/// /summary
/// typeparam nameT字段类型/typeparam
/// remarks
/// 该类主要用于创造某个字段的副本作为该字段的临时缓存避免直接修改源字段。
/// /remarks
public class TempWrapperT : IDisposable
{/// summary/// 是否为值类型/// para提示若为true则表示包装字段为值类型否则为引用类型/para/// /summarypublic static bool isValueType _isValueType;/// summary/// 缓存字段/// para提示对于值类型而言该属性涉及拷贝/para/// /summarypublic 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/// /summarypublic ref T refrence{get{if (_isDisposed) throw new InvalidOperationException(The wrapper is disposed.);return ref _value;}}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 namevalue待包装字段的引用/param/// remarks/// para提示采用二进制序列化和反序列化生成字段副本/para/// para提示该方法仅可用于被 cSerializable/c 标记的字段类型/para/// /remarkspublic static TempWrapperT WrapByBinary(ref T value){lock (_key){try{TempWrapperT wrapper new TempWrapperT();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 namevalue待包装字段的引用/parampublic static TempWrapperT WrapByJson(ref T value){lock (_key){try{TempWrapperT wrapper new TempWrapperT();if (_isValueType) wrapper._value value;else{string jsonStr JsonConvert.SerializeObject(value);wrapper._value JsonConvert.DeserializeObjectT(jsonStr);}return wrapper;}catch (Exception e){throw new InvalidOperationException(Failed to wrap., e);}}}/// summary/// 包装生成器所生成的字段并返回包装类/// /summary/// param namecreator生成器/parampublic static TempWrapperT WrapByCustom(FuncT creator){lock (_key){try{TempWrapperT wrapper new TempWrapperT() { _value creator() };return wrapper;}catch (Exception e){throw new InvalidOperationException(Failed to wrap., e);}}}/// summary/// 解包包装器并赋值给指定的字段/// /summary/// remarks/// para提示采用二进制序列化和反序列化解包/para/// para提示该方法仅可用于被 cSerializable/c 标记的字段类型/para/// /remarkspublic void UnWrapByBinary(ref T value){if (_isDisposed) throw new InvalidOperationException(The wrapper is disposed.);if (_isValueType) value _value;else{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/// /summarypublic void UnwrapByJson(ref T value){if (_isDisposed) throw new InvalidOperationException(The wrapper is disposed.);if (_isValueType) value _value;else{string jsonStr JsonConvert.SerializeObject(_value);value JsonConvert.DeserializeObjectT(jsonStr);}}/// summary/// 释放包装器所包装的字段/// para提示当所包装字段实现了IDisposable接口时该方法才有效/para/// /summarypublic void Dispose(){if (_isDisposed) throw new InvalidOperationException(The wrapper is disposed.);DoDispose(true);GC.SuppressFinalize(this);}void DoDispose(bool disposing){if (_isDisposed) return;_isDisposed true;if (disposing _isDisposable _value is IDisposable ds)ds.Dispose();}~TempWrapper(){DoDispose(false);}
}
测试
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;// TempWrapper测试脚本
public class TempWrapperTest : MonoBehaviour
{[SerializeField] int[] intArray;[SerializeField] string[] strArray;[SerializeField] StructA[] structaArray;[SerializeField] ClassA classA;[SerializeField] StructA structA;[SerializeField] Transform tf;[Serializable]struct StructA{public string key;public int value;public override string ToString(){return $(key:{key},value:{value});}}[Serializable]class ClassA{public string key;public StructA structA;public override string ToString(){StringBuilder builder new StringBuilder([key:);builder.Append(key).Append(,).Append($StructA:{structA});builder.Append(]);return builder.ToString();}}TempWrapperint[] intArrayWrapper;TempWrapperstring[] strArrayWrapper;TempWrapperStructA[] structaArrayWrapper;TempWrapperClassA classaWrapper;TempWrapperStructA structaWrapper;TempWrapperTransform tfWrapper;void Awake(){if (Application.isPlaying){intArrayWrapper TempWrapperint[].WrapByBinary(ref intArray);strArrayWrapper TempWrapperstring[].WrapByJson(ref strArray);structaArrayWrapper TempWrapperStructA[].WrapByBinary(ref structaArray);classaWrapper TempWrapperClassA.WrapByBinary(ref classA);structaWrapper TempWrapperStructA.WrapByBinary(ref structA);tfWrapper TempWrapperTransform.WrapByCustom(() Instantiate(tf));Instantiate(transform);}}void OnDestroy(){intArrayWrapper.Dispose();strArrayWrapper.Dispose();structaArrayWrapper.Dispose();classaWrapper.Dispose();structaWrapper.Dispose();tfWrapper.Dispose();}void Update(){if (Input.GetKeyDown(KeyCode.Q)){PrintWrapper();PrintHashCode();PrintWrapperHashCode();}if (Input.GetKeyDown(KeyCode.W)){WriteWrapper();UnWrap();}}void PrintWrapper(){intArrayWrapper.value.LogC(IntArrayWrapper:);strArrayWrapper.value.LogC(StrArrayWrapper:);structaArrayWrapper.value.LogC(s $[key:{s.key},value:{s.value}], StructaArrayWrapper:);LogUtility.Log(ClassAWrapper: classaWrapper.value);LogUtility.Log(StructAWrapper: structaWrapper.value);LogUtility.Log(TfWrapper: tfWrapper.value.position);}void PrintHashCode(){LogUtility.Log(IntArray: intArray.GetHashCode());LogUtility.Log(StrArray: strArray.GetHashCode());LogUtility.Log(StructaArray: structaArray.GetHashCode());LogUtility.Log(ClassA: classA.GetHashCode());LogUtility.Log(StructA: structA.GetHashCode());LogUtility.Log(Tf: tf.GetHashCode());}void PrintWrapperHashCode(){LogUtility.Log(IntArrayWrapper: intArrayWrapper.value.GetHashCode());LogUtility.Log(StrArrayWrapper: strArrayWrapper.value.GetHashCode());LogUtility.Log(StructaArrayWrapper: structaArrayWrapper.value.GetHashCode());LogUtility.Log(ClassAWrapper: classaWrapper.value.GetHashCode());LogUtility.Log(StructAWrapper: structaWrapper.value.GetHashCode());LogUtility.Log(TfWrapper: tfWrapper.value.GetHashCode());}void WriteWrapper(){Listint ints new Listint(intArrayWrapper.value) { 99, 100 };intArrayWrapper.value ints.ToArray();Liststring strs new Liststring(strArrayWrapper.value) { D, E };strArrayWrapper.value strs.ToArray();ListStructA strcutAs new ListStructA(structaArrayWrapper.value){new StructA { key D, value 99 },new StructA { key E, value 100 }};structaArrayWrapper.value strcutAs.ToArray();structaWrapper.refrence.key E;structaWrapper.refrence.value 1000;classaWrapper.value.key DE;classaWrapper.value.structA.key D;classaWrapper.value.structA.value 999;tfWrapper.value.position Vector3.zero;}void UnWrap(){intArrayWrapper.UnWrapByBinary(ref intArray);strArrayWrapper.UnwrapByJson(ref strArray);structaArrayWrapper.UnWrapByBinary(ref structaArray);structaWrapper.UnWrapByBinary(ref structA);classaWrapper.UnWrapByBinary(ref classA);}
}
#endif
v1.0
用例ID用例名称前者测试预期结果是否通过1简单值类型数组无可缓存通过2不可变引用类型数组无可缓存通过3复合值类型数组无可缓存通过4自定义引用类型无可缓存通过5自定义值类型无可缓存通过6Unity对象无可缓存通过
v1.1
用例ID用例名称前者测试预期结果是否通过1简单值类型数组无可缓存通过2不可变引用类型数组无可缓存通过3复合值类型数组无可缓存通过4自定义引用类型无可缓存通过5自定义值类型无可缓存通过6Unity对象无可缓存通过
分析 字段临时缓存包装器有三种包装字段的方式分别是WrapByBinary、WrapByJson和WrapByCustom三种方式各有优缺点择优而用。WrapByBinary采用二进制序列化和反序列化生成字段副本该方法仅可用于被 Serializable 标记的字段类型。WrapByJson采用JSON序列化和反序列化生成字段副本它虽然比前者包装范围更广但是不可避免可能会依赖第三方用于JSON序列化和反序列化的库。WrapByCustom则是对前两种方式的补充当前两种方式都不适用时则可以自定义包装方式例如对于Unity对象来说需要通过Instantiate方法创建对象副本这个时候就只能用自定义的方法进行包装。 返回的包装器提供了一些属性和方法可用于判断是否为值类型、缓存的字段和缓存字段的引用值类型提供了针对WrapByBinary和WrapByJson包装方法的解包方法还提供了显式释放包装器的方法。对于包装值类型时我们可以通过获取缓存字段的引用来避免拷贝解包方法用于将临时缓存的数据重新写入被包装字段中。通过显式释放包装器可以保证那些使用了非托管资源的类型实现了IDisposable接口进行资源的释放工作从而避免内存泄漏等问题。 但是该包装器存在一些不可避免的限制若所包装字段越复杂其性能损耗越高这是不可避免的。而对于复杂类型建议自定义一个数据结构作为临时缓存的包装类型。 注意不要使用包装器去包装其声明所在的类特别是对于自定义包装逻辑要避免无限递归否则会导致栈溢出或内存泄漏问题。示例代码如下 // 该方法将导致栈溢出
public class A
{TempWrapperA wrapper;public A(){wrapper TempWrapperA.WrapByCustom(() new A());}
}// 该方法将导致内存泄漏
public class B:Monobehaviour
{TempWrapperTransform wrapper;void Awake(){// Instantiate方法去克隆当前组件对象的Transform组件就会导致无限递归wrapper TempWrapperTransform.WrapByCustom(() Instantiate(transform));}
} 版本改进
版本号改进内容v1.1 1.实例方法不使用线程锁仅对静态方法使用线程锁 2.手动执行Dispose方法释放包装器后所有对公开实例成员的访问都将触发异常 3.删除IsDisposed属性 ............
系列文章
......
如果这篇文章对你有帮助请给作者点个赞吧