Last active
November 13, 2018 11:13
-
-
Save mikernet/c68c1251d6a7e4018cee85783528b589 to your computer and use it in GitHub Desktop.
Provides super fast casting support that avoids boxing with compiled expressions
This file contains 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
// Caster by Mike Marynowski | |
// Licensed under the Code Project Open License: http://www.codeproject.com/info/cpol10.aspx | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq.Expressions; | |
namespace Singulink.Reflection | |
{ | |
using CasterCache = Dictionary<ValueTuple<Type, Type>, Func<object, object>>; | |
public static class Caster | |
{ | |
private static readonly CasterCache _casterCache = new CasterCache(); | |
private static readonly CasterCache _checkedCasterCache = new CasterCache(); | |
/// <summary> | |
/// Determines if one type can be cast to another type. | |
/// </summary> | |
/// <param name="fromType">The input object type.</param> | |
/// <param name="toType">The output object type.</param> | |
/// <returns>True if a valid cast exists, otherwise false.</returns> | |
public static bool IsValidCast(Type fromType, Type toType) | |
{ | |
if (fromType == null) | |
throw new ArgumentNullException(nameof(fromType)); | |
if (toType == null) | |
throw new ArgumentNullException(nameof(toType)); | |
try { | |
Expression.Convert(Expression.Parameter(fromType, "fromObj"), toType); | |
} | |
catch (InvalidOperationException) { | |
return false; | |
} | |
return true; | |
} | |
public static TTo Cast<TFrom, TTo>(TFrom value) => GenericCache<TFrom, TTo>.Cast(value); | |
public static TTo Cast<TFrom, TTo>(TFrom value, bool checkOverflow) => checkOverflow ? GenericCache<TFrom, TTo>.CheckedCast(value) : GenericCache<TFrom, TTo>.Cast(value); | |
public static TTo CheckedCast<TFrom, TTo>(TFrom value) => GenericCache<TFrom, TTo>.CheckedCast(value); | |
public static T DynamicCast<T>(object obj, bool checkOverflow) => checkOverflow ? DynamicCheckedCast<T>(obj) : DynamicCast<T>(obj); | |
public static T DynamicCast<T>(object obj) => (T)(dynamic)obj; | |
public static T DynamicCheckedCast<T>(object obj) => checked((T)(dynamic)obj); | |
public static object DynamicCast(this object obj, Type toType, bool checkOverflow = false) | |
{ | |
if (toType == null) | |
throw new ArgumentNullException(nameof(toType)); | |
if (obj == null) { | |
if (toType.IsValueType && Nullable.GetUnderlyingType(toType) == null) | |
throw new ArgumentNullException(nameof(obj), $"Cannot cast a null value to value type '{toType}'."); | |
return null; | |
} | |
return GetCaster(obj.GetType(), toType, checkOverflow).Invoke(obj); | |
} | |
public static Func<object, object> GetCaster(Type fromType, Type toType, bool checkOverflow) => checkOverflow ? GetCaster(fromType, toType) : GetCheckedCaster(fromType, toType); | |
public static Func<object, object> GetCaster(Type fromType, Type toType) => GetCaster(fromType, toType, _casterCache); | |
public static Func<object, object> GetCheckedCaster(Type fromType, Type toType) => GetCaster(fromType, toType, _checkedCasterCache); | |
private static Func<object, object> GetCaster(Type fromType, Type toType, CasterCache casterCache) | |
{ | |
if (fromType == null) | |
throw new ArgumentNullException(nameof(fromType)); | |
if (toType == null) | |
throw new ArgumentNullException(nameof(toType)); | |
var key = ValueTuple.Create(fromType, toType); | |
lock (casterCache) { | |
if (!casterCache.TryGetValue(key, out var caster)) { | |
caster = Create<object, object>(fromType, toType); | |
if (caster == null) | |
throw new InvalidCastException(); | |
casterCache.Add(key, caster); | |
} | |
return caster; | |
} | |
} | |
private static Func<TIn, TOut> Create<TIn, TOut>(Type fromType, Type toType, bool checkedOverflow = false) | |
{ | |
if (!IsValidCast(fromType, toType)) | |
return null; | |
Expression body; | |
var parameter = Expression.Parameter(typeof(TIn), "in"); | |
var input = typeof(TIn) == fromType ? (Expression)parameter : Expression.Convert(parameter, fromType); | |
if (fromType == toType) | |
body = parameter; | |
else | |
body = checkedOverflow ? Expression.ConvertChecked(input, toType) : Expression.Convert(input, toType); | |
if (typeof(TOut) != toType) | |
body = Expression.Convert(body, typeof(TOut)); | |
return Expression.Lambda<Func<TIn, TOut>>(body, parameter).Compile(); | |
} | |
private static class GenericCache<TFrom, TTo> | |
{ | |
private static readonly Func<TFrom, TTo> _caster = Create<TFrom, TTo>(typeof(TFrom), typeof(TTo), false); | |
private static readonly Func<TFrom, TTo> _checkedCaster = CanCheck ? Create<TFrom, TTo>(typeof(TFrom), typeof(TTo), true) : _caster; | |
private static bool CanCheck => IsPrimitiveOrEnum(typeof(TFrom)) && IsPrimitiveOrEnum(typeof(TTo)); | |
public static TTo Cast(TFrom value) => (_caster ?? throw new InvalidCastException()).Invoke(value); | |
public static TTo CheckedCast(TFrom value) => (_checkedCaster ?? throw new InvalidCastException()).Invoke(value); | |
private static bool IsPrimitiveOrEnum(Type type) => type.IsPrimitive || type.IsEnum; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment