Skip to content

Instantly share code, notes, and snippets.

@mikernet
Last active November 13, 2018 11:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikernet/c68c1251d6a7e4018cee85783528b589 to your computer and use it in GitHub Desktop.
Save mikernet/c68c1251d6a7e4018cee85783528b589 to your computer and use it in GitHub Desktop.
Provides super fast casting support that avoids boxing with compiled expressions
// 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