Ceci est similaire à réponses postées ici, mais utilise l'expression arbres émettent il à coulé entre les types. Expression.Convert
fait le tour. La compilation des délégué (caster) est mis en cache par un intérieur de classe statique. Depuis la source de l'objet peut être déduite à partir de l'argument, je suppose qu'il offre nettoyeur appel. Par exemple, un générique contexte:
static int Generic<T>(T t)
{
int variable = -1;
// may be a type check - if(...
variable = CastTo<int>.From(t);
return variable;
}
La classe:
/// <summary>
/// Class to cast to type <see cref="T"/>
/// </summary>
/// <typeparam name="T">Target type</typeparam>
public static class CastTo<T>
{
/// <summary>
/// Casts <see cref="S"/> to <see cref="T"/>.
/// This does not cause boxing for value types.
/// Useful in generic methods
/// </summary>
/// <typeparam name="S">Source type to cast from. Usually a generic type.</typeparam>
public static T From<S>(S s)
{
return Cache<S>.caster(s);
}
static class Cache<S>
{
internal static readonly Func<S, T> caster = Get();
static Func<S, T> Get()
{
var p = Expression.Parameter(typeof(S));
var c = Expression.ConvertChecked(p, typeof(T));
return Expression.Lambda<Func<S, T>>(c, p).Compile();
}
}
}
Vous pouvez remplacer l' caster
func avec d'autres implémentations. Je vais comparer les performances de quelques-uns:
direct object casting, ie, (T)(object)S
caster1 = (Func<T, T>)(x => x) as Func<S, T>;
caster2 = Delegate.CreateDelegate(typeof(Func<S, T>), ((Func<T, T>)(x => x)).Method) as Func<S, T>;
caster3 = my implementation above
caster4 = EmitConverter();
static Func<S, T> EmitConverter()
{
var method = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(S) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
if (typeof(S) != typeof(T))
{
il.Emit(OpCodes.Conv_R8);
}
il.Emit(OpCodes.Ret);
return (Func<S, T>)method.CreateDelegate(typeof(Func<S, T>));
}
Boîte de moulages:
-
int
de int
objet de la coulée -> 42 ms
caster1 -> 102 ms
caster2 -> 102 ms
caster3 -> 90 ms
caster4 -> 101 ms
-
int
de int?
objet de la coulée -> 651 ms
caster1 -> fail
caster2 -> fail
caster3 -> 109 ms
caster4 -> fail
-
int?
de int
objet de la coulée -> 1957 ms
caster1 -> fail
caster2 -> fail
caster3 -> 124 ms
caster4 -> fail
-
enum
de int
objet de la coulée -> 405 ms
caster1 -> fail
caster2 -> 102 ms
caster3 -> 78 ms
caster4 -> fail
-
int
de enum
objet de la coulée -> 370 ms
caster1 -> fail
caster2 -> 93 ms
caster3 -> 87 ms
caster4 -> fail
-
int?
de enum
objet de la coulée -> 2340 ms
caster1 -> fail
caster2 -> fail
caster3 -> 258 ms
caster4 -> fail
-
enum?
de int
objet de la coulée -> 2776 ms
caster1 -> fail
caster2 -> fail
caster3 -> 131 ms
caster4 -> fail
Expression.Convert
met directement en fonte de type de la source à la cible, donc il peut travailler explicites et implicites jette (pour ne pas mentionner la référence plâtres). Donc, cela laisse la place pour la manipulation de la coulée qui est par ailleurs possible que lorsque la non-boîte (c'est à dire, une méthode générique si vous n' (TTarget)(object)(TSource)
il va exploser si elle n'est pas de l'identité de conversion (comme dans la section précédente) ou à la conversion de référence (comme indiqué à la section ultérieure)). Donc, je vais les inclure dans les tests.
Non encadré jette:
-
int
de double
objet de la coulée -> fail
caster1 -> fail
caster2 -> fail
caster3 -> 109 ms
caster4 -> 118 ms
-
enum
de int?
objet de la coulée -> fail
caster1 -> fail
caster2 -> fail
caster3 -> 93 ms
caster4 -> fail
-
int
de enum?
objet de la coulée -> fail
caster1 -> fail
caster2 -> fail
caster3 -> 93 ms
caster4 -> fail
-
enum?
de int?
objet de la coulée -> fail
caster1 -> fail
caster2 -> fail
caster3 -> 121 ms
caster4 -> fail
-
int?
de enum?
objet de la coulée -> fail
caster1 -> fail
caster2 -> fail
caster3 -> 120 ms
caster4 -> fail
Pour le fun, j'ai testé un peu de type de référence des conversions:
-
PrintStringProperty
de string
(représentation, modification)
objet de la coulée -> fail (tout à fait évident, puisqu'il n'est pas précipités sur le type d'origine)
caster1 -> fail
caster2 -> fail
caster3 -> 315 ms
caster4 -> fail
-
string
de object
(représentation de la préservation de conversion de référence)
objet de la coulée -> 78 ms
caster1 -> fail
caster2 -> fail
caster3 -> 322 ms
caster4 -> fail
Testé comme ceci:
static void TestMethod<T>(T t)
{
CastTo<int>.From(t); //computes delegate once and stored in a static variable
int value = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
value = (int)(object)t;
// similarly value = CastTo<int>.From(t);
// etc
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
}
Note:
Mon estimation est que, à moins que vous exécutez au moins une centaine de milliers de fois, c'est pas la peine, et vous avez presque rien à vous soucier de la boxe. Vous l'esprit la mise en cache des délégués a un impact sur la mémoire. Mais au-delà de cette limite, l'amélioration de la vitesse est très importante, surtout quand il s'agit de la coulée impliquant nullable.
Mais le véritable avantage de l' CastTo<T>
classe, c'est quand il permet de moulages qui sont possibles non encadré, comme (int)double
dans un contexte générique. Comme tel, (int)(object)double
d'échec dans ces scénarios.
Je l'ai utilisé, Expression.ConvertChecked
au lieu de Expression.Convert
alors que l'arithmétique, des débordements et des underflows sont vérifiées (c'est à dire les résultats de l'exception). Car il est généré lors de l'exécution, et les paramètres enregistrés sont un moment de la compilation chose, il n'y a aucun moyen de savoir l'objet d'contexte de l'appel de code. C'est quelque chose que vous avez à vous de décider. Choisissez-en une, ou de fournir une surcharge pour les deux (mieux).
Si une distribution n'existe pas de TSource
de TTarget
, exception est levée alors que le délégué est compilé. Si vous souhaitez un comportement différent, comme obtenir une valeur par défaut de TTarget
, vous pouvez vérifier la compatibilité du type de l'utilisation de la réflexion avant de compiler délégué. Vous avez le plein contrôle sur le code généré. Sa va être extrêmement compliqué cependant, vous devez vérifier la référence de compatibilité (IsSubClassOf
, IsAssignableFrom
), opérateur de conversion de l'existence (va être hacky), et même, pour certains, construit dans le type de la convertibilité entre les types primitifs. Va être extrêmement hacky. Plus facile est de prendre de l'exception et de retourner la valeur par défaut de délégué basé sur ConstantExpression
. Tout en indiquant une possibilité que vous pouvez imiter le comportement d' as
mot-clé qui ne marche pas à jeter. De son mieux pour rester loin de lui et s'en tenir à la convention.