Comment créer une méthode de produit scalaire générique pour différents types de nombres
J'ai la fonction suivante:
public static Func<int[], int[], int> Foo()
{
Func<int[], int[], int> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
Je voudrais créer le même Func mais pour différents types de nombres (long, court, etc. et pas seulement int).
Le code ci-dessous ne fonctionne pas. Je reçois l'erreur suivante (CS0019 : l'opérateur '*' ne peut pas être appliqué aux opérandes de type 'T' et 'T') :
public static Func<T[], T[], T> Foo<T>() where T: struct
{
Func<T[], T[], T> result = (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
Après quelques recherches, j'ai conclu que je devais générer du code dynamiquement avec des arbres d'expression, cependant, je n'ai trouvé aucune ressource utile sur le Web. Ceux que j'ai trouvés ne traitent que d'expressions lambda très simples. J'ai également essayé d'utiliser reflection<> et ILSpy pour jeter un coup d'œil dans le code C#1 automatiquement avec l'idée de changer manuellement les ints en Ts. Cependant, cela n'a pas fonctionné - je pense qu'en raison de (RuntimeMethodHandle)/ OpCode non pris en charge : LdMemberToken /. Toute aide serait appréciée. Je suis vraiment intéressé à résoudre ce problème.
public static Expression<Func<int[], int[], int>> Foo()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(int[]), "first");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int[]), "second");
MethodInfo method = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] array = new Expression[1];
MethodInfo method2 = (MethodInfo)MethodBase.GetMethodFromHandle((RuntimeMethodHandle)/*OpCode not supported: LdMemberToken*/);
Expression[] obj = new Expression[3] { parameterExpression, parameterExpression2, null };
ParameterExpression parameterExpression3 = Expression.Parameter(typeof(int), "x");
ParameterExpression parameterExpression4 = Expression.Parameter(typeof(int), "y");
obj[2] = Expression.Lambda<Func<int, int, int>>(Expression.Multiply(parameterExpression3, parameterExpression4), new ParameterExpression[2] { parameterExpression3, parameterExpression4 });
array[0] = Expression.Call(null, method2, obj);
return Expression.Lambda<Func<int[], int[], int>>(Expression.Call(null, method, array), new ParameterExpression[2] { parameterExpression, parameterExpression2 });
}
Solution du problème
Les mathématiques génériques sont une fonctionnalité à venir qui est actuellement en préversion. Ainsi, à l'avenir, les "membres d'interface abstraits statiques" sont le moyen de gérer cela. Si vous activez les fonctionnalités d'aperçu, vous pouvez écrire du code C# valide comme celui-ci :
public static Func<T[], T[], T> Foo<T>()
where T:
unmanaged,
IMultiplyOperators<T, T, T>,
IAdditiveIdentity<T, T>,
IAdditionOperators<T, T, T>
{
Func<T[], T[], T> result =
static (first, second) => first.Zip(second, (x, y) => x * y).Sum();
return result;
}
// generic sum doesn't exist yet in linq
public static T Sum<T>(this IEnumerable<T> source)
where T:
unmanaged,
IAdditionOperators<T, T, T>,
IAdditiveIdentity<T, T>
{
T sum = T.AdditiveIdentity;
foreach (var item in source)
{
sum += item;
}
return sum;
}
Il faudra encore un certain temps avant la sortie des maths génériques et il y a des problèmes non résolus (comme l'impossibilité de faire des maths "vérifiées"), donc pour répondre à votre question, pourquoi ne pas simplement utiliser dynamique?
public static Func<T[], T[], T> Foo<T>() where T: struct
{
Func<T[], T[], T> result = (first, second) => DynamicDotProduct(first.Zip(second));
return result;
}
private static T DynamicDotProduct<T>(IEnumerable<(T first, T second)> zipped) where T: struct
{
// here I am assuming default(T) is zero of that type
dynamic sum = default(T);
foreach((dynamic x, T y) in zipped)
{
sum += x * y;
}
return sum;
}
Commentaires
Enregistrer un commentaire