Last active
March 22, 2021 00:36
-
-
Save mikernet/d8884f3a9ca30e85c9a27fdeaa695a90 to your computer and use it in GitHub Desktop.
Value Type Box Cache
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Box Cache implementation by Mike Marynowski | |
// View the article here: http://www.singulink.com/CodeIndex/post/value-type-box-cache | |
// Licensed under the Code Project Open License: http://www.codeproject.com/info/cpol10.aspx | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Diagnostics; | |
using System.Reflection; | |
namespace Singulink | |
{ | |
public static class BoxCache | |
{ | |
static int _intialized; | |
internal static void IntializeDefaults() | |
{ | |
if (Interlocked.CompareExchange(ref _intialized, 1, 0) == 0) | |
{ | |
// If you are only caching a default value then don't bother doing it here, | |
// it gets automatically added in the generic BoxCache<T> initializer. | |
AddValues(false, true); | |
AddValues(Enumerable.Range(0, 255).Select(v => (byte)v)); | |
AddValues(Enumerable.Range(-128, 127).Select(v => (sbyte)v)); | |
AddValues(Enumerable.Range(-10, 256).Select(v => (short)v)); | |
AddValues(Enumerable.Range(0, 256).Select(v => (ushort)v)); | |
AddValues(Enumerable.Range(-10, 256).Select(v => (int)v)); | |
AddValues(Enumerable.Range(0, 256).Select(v => (uint)v)); | |
AddValues(Enumerable.Range(-10, 256).Select(v => (long)v)); | |
AddValues(Enumerable.Range(0, 256).Select(v => (ulong)v)); | |
AddValues<float>(-1, 0, 1); | |
AddValues<double>(-1, 0, 1); | |
AddValues<decimal>(-1, 0, 1); | |
} | |
} | |
#region Public Methods | |
/// <summary> | |
/// Gets a cached box for the specified value if one is cached, otherwise it returns a new box for the value. | |
/// </summary> | |
public static object GetBox<T>(T value) => BoxCache<T>.GetBox(value); | |
/// <summary> | |
/// Gets a cached box for the specified value. A box is created and added to the cache if it doesn't already exist. | |
/// </summary> | |
public static object GetOrAddBox<T>(T value) => BoxCache<T>.GetOrAddBox(value); | |
/// <summary> | |
/// Adds a box for the specified value into the cache. Note that the cache uses a copy-on-write mechanism to ensure thread safety and | |
/// ensure fastest possible lookup time so use this sparingly and use 'AddValues()' for multiple values instead. | |
/// </summary> | |
public static void AddValue<T>(T value) where T : struct => BoxCache<T>.AddValue(value); | |
/// <summary> | |
/// Adds boxes for the specified values into the cache. Note that the cache uses a copy-on-write mechanism to ensure thread safety and | |
/// ensure fastest possible lookup time so use this sparingly after application startup. | |
/// </summary> | |
public static void AddValues<T>(IEnumerable<T> values) where T : struct, IComparable<T> => BoxCache<T>.AddValues(values); | |
#endregion | |
#region Helpers | |
static void AddValues<T>(params T[] values) where T : struct => BoxCache<T>.AddValues(values); | |
#endregion | |
} | |
static class BoxCache<T> | |
{ | |
static Dictionary<T, object> _boxLookup; | |
static Func<T, object> _getNullableBox; | |
static Func<T, object> _getOrAddNullableBox; | |
static readonly object _syncRoot = new object(); | |
static readonly bool _init = Initialize(); // Avoid static constructor performance penalty | |
#region Initializer | |
static bool Initialize() | |
{ | |
if (!typeof(T).IsValueType) | |
throw new InvalidOperationException("Only value types can be boxed."); | |
if (typeof(T).IsNullable()) { | |
// This is nullable so force static initializer for the underlying type cache to run so default boxes get populated there | |
// and the delegates for this class are assigned | |
var valueType = Nullable.GetUnderlyingType(typeof(T)); | |
Type boxCacheEnumType = typeof(BoxCache<>).MakeGenericType(valueType); | |
System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(boxCacheEnumType.TypeHandle); | |
// Assign delegates so box requests are forwarded to the non-nullable cache | |
var underlyingType = Nullable.GetUnderlyingType(typeof(T)); | |
var underlyingCacheType = typeof(BoxCache<>).MakeGenericType(underlyingType); | |
var getMethod = underlyingCacheType.GetMethod(nameof(GetNullableBox), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(underlyingType); | |
var getOrAddMethod = underlyingCacheType.GetMethod(nameof(GetOrAddNullableBox), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(underlyingType); | |
_getNullableBox = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), getMethod); | |
_getOrAddNullableBox = (Func<T, object>)Delegate.CreateDelegate(typeof(Func<T, object>), getOrAddMethod); | |
} | |
else { | |
// Ensure default boxes are initialized | |
BoxCache.IntializeDefaults(); | |
if (typeof(T).IsEnum) { | |
var values = (T[])Enum.GetValues(typeof(T)); | |
AddValues(values); | |
} | |
if (_boxLookup == null) | |
_boxLookup = new Dictionary<T, object>(); | |
if (!_boxLookup.ContainsKey(default)) | |
_boxLookup.Add(default, default(T)); | |
} | |
return true; | |
} | |
#endregion | |
internal static object GetBox(T value) | |
{ | |
if (_boxLookup == null) { | |
Debug.Assert(typeof(T).IsNullable(), "box lookup should only be null for nullable types"); | |
if (value == null) | |
return null; | |
return _getNullableBox(value); | |
} | |
Debug.Assert(!typeof(T).IsNullable(), "box lookup should not be created for nullable types"); | |
if (_boxLookup.TryGetValue(value, out object obj)) | |
return obj; | |
return value; | |
} | |
internal static object GetOrAddBox(T value) | |
{ | |
if (_boxLookup == null) { | |
Debug.Assert(typeof(T).IsNullable(), "box lookup should only be null for nullable types"); | |
if (value == null) | |
return null; | |
return _getOrAddNullableBox(value); | |
} | |
Debug.Assert(!typeof(T).IsNullable(), "box lookup should not be created for nullable types"); | |
if (_boxLookup.TryGetValue(value, out object obj)) | |
return obj; | |
object box = AddValue(value); | |
return box; | |
} | |
internal static void AddValues(IEnumerable<T> values) | |
{ | |
Debug.Assert(!typeof(T).IsNullable(), "nullable types should always be forwarded to non-nullable cache"); | |
if (values == null) | |
throw new ArgumentNullException(nameof(values)); | |
lock (_syncRoot) { | |
// Avoid copying the dictionary if there are no new values. | |
Dictionary<T, object> newBoxLookup = _boxLookup == null ? new Dictionary<T, object>() : null; | |
foreach (var value in values) { | |
if (newBoxLookup != null) { | |
if (!newBoxLookup.ContainsKey(value)) | |
newBoxLookup.Add(value, value); | |
} | |
else if (!_boxLookup.ContainsKey(value)) { | |
newBoxLookup = new Dictionary<T, object>(_boxLookup) { { value , value } }; | |
} | |
} | |
_boxLookup = newBoxLookup; | |
} | |
} | |
internal static object AddValue(T value) | |
{ | |
Debug.Assert(!typeof(T).IsNullable(), "nullable types should always be forwarded to non-nullable cache"); | |
lock (_syncRoot) { | |
if (_boxLookup.TryGetValue(value, out object obj)) | |
return obj; | |
object box = value; | |
var newBoxLookup = new Dictionary<T, object>(_boxLookup) { { value, box } }; | |
_boxLookup = newBoxLookup; | |
return box; | |
} | |
} | |
// Forwarding methods for nullable types | |
private static object GetNullableBox<TNullable>(TNullable? value) where TNullable : struct | |
{ | |
Debug.Assert(value.HasValue, "value required"); | |
return GetBox((T)(object)value.Value); | |
} | |
private static object GetOrAddNullableBox<TNullable>(TNullable? value) where TNullable : struct | |
{ | |
Debug.Assert(value.HasValue, "value required"); | |
return GetOrAddBox((T)(object)value.Value); | |
} | |
} | |
public static class Helpers | |
{ | |
public static bool IsNullable(this Type type) => Nullable.GetUnderlyingType(type) != null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment