J'ai le code suivant pour C ++, dans une classe basée sur un modèle qui représente un point. Je voudrais le traduire en C #:
public class Point<T> { public T X { get; set; } public T Y { get; set; } public T Z { get; set; } // Constructors exist that accept X, Y, and Z as parameters public static explicit operator Point<U>(Point<T> p) { } }
Ce code permet à un point d'un type donné d'être explicitement converti en un point d'un autre type. Par exemple, vous pouvez l'utiliser quelque chose comme (certes, je ne suis pas sûr à 100% de la syntaxe ici, mais je comprends le concept):
Point<float> p; Point<int> q = (Point<int>)p;
Comment pourrais-je activer l'équivalent à cela en C #? Jusqu'à présent, j'ai:
template <class T> class Point { public: T x; T y; T z; template<typename U> explicit Point(const Point<U> &p) : x((T)p.x), y((T)p.y), z((T)p.z) { } }
Cela donne une erreur, cependant, dire "U" n'est pas défini. Cela a du sens ... mais comment / où définir U? Mon approche est-elle incorrecte?
La différence entre ma question et celle ici , c'est que je change simplement le type de sous-couche de la classe générique via un cast ... sans essayer de changer une classe générique en une classe générique différente avec le même type de sous-couche. p>
5 Réponses :
Autant que je sache, ce type de conversion générique n'est autorisé en C # que s'il existe une sorte de relation d'héritage entre T
et U
.
Le plus proche l'équivalent serait de définir une méthode générique pour la conversion:
public Point<U> To<U>() { dynamic p = this; return new Point<U>((U)p.X, (U)p.Y, (U)p.Z); }
Vous ne pouvez pas convertir directement T
en U
en tant que Le compilateur n'a aucun moyen de savoir s'il sera sûr. J'utilise le mot-clé dynamic
pour contourner cette restriction.
Cela semble alors plus générique, car il ne nécessitera pas de types numériques. Mon problème arrive à (seuls les types numériques seront utilisés)… mais c'est bon à savoir pour l'avenir si ce n'est pas le cas. Je me demande quelle est la différence de performance entre cette option et l'option Convert.ChangeType
.
@MichaelKintscher C'est une bonne question. J'ai fait un test rapide, j'obtiens 127ns pour Convert.ChangeType
contre 53ns pour Dynamic
. Notez que le premier appel à dynamic est beaucoup plus cher (car l'expression est évaluée lors de la première invocation puis mise en cache)
Bon à savoir! J'ai également ajouté where U: IConvertible
comme contrainte à la fois sur T
et U
, juste pour être sûr pour le consommateur de ma classe.
Je pense que le mieux que vous puissiez obtenir est le suivant:
var p1 = new Point<int> { X = 1, Y = 2, Z = 3 }; var p2 = p1.As<double>();
Vous ne pouvez pas définir un opérateur de conversion générique, vous en avez donc besoin pour être une fonction explicite. De plus, un simple cast (U) t
ne fonctionnera pas, vous avez donc besoin de Convert.ChangeType
(qui fonctionnera si vos types sont numériques).
Utilisation: p>
public class Point<T> { public T X { get; set; } public T Y { get; set; } public T Z { get; set; } public Point<U> As<U>() { return new Point<U>() { X = Convert<U>(X), Y = Convert<U>(Y), Z = Convert<U>(Z) }; } static U Convert<U>(T t) => (U)System.Convert.ChangeType(t, typeof(U)); }
Notez que Convert.ChangeType
ne fonctionne que pour les types qui implémentent IConvertible
. Ce qui est parfaitement bien si OP colle avec des types primitifs
@KevinGosse: oui, je dis dans ma réponse "qui fonctionnera si vos types sont numériques". Merci pour l'indice!
Semblable à la réponse de Kevin , mais sans dynamique
est d'utiliser une double distribution: < pre> XXX
Nos deux réponses ne détectent aucun problème lors de la compilation.
Cela ne fonctionnera pas, vous devez déballer la valeur avant de la diffuser. int i = (int) (object) 1.0f;
lance une InvalidCastException
Hm. Oh ouais tu as raison. J'utilise le double-cast dans un tas de modèles génériques, mais cela a toujours été pour les types de référence.
Vous ne pouvez pas déclarer d'opérateurs avec des arguments de type générique supplémentaires, mais vous pouvez en déclarer des vers ou à partir de types génériques spécifiques comme Point
. C # ne vous permettra pas non plus d'effectuer des conversions arbitraires en castant depuis ou vers T
.
L'option la moins lourde standard qui maintient un minimum de sécurité de type serait de contraindre le code T > à
IConvertible
:
public class Point<T> where T : IConvertible { // ... public static explicit operator Point<int>(Point<T> point) { // The IFormatProvider parameter has no effect on purely numeric conversions return new Point<int>(point.X.ToInt32(null), point.Y.ToInt32(null), point.Y.ToInt32(null)); } }
Cependant, cela n'empêchera pas les utilisateurs de déclarer des types absurdes et non pris en charge tels que Point
Vous ne pouvez pas définir de contraintes de type générique supplémentaires, mais vous pouvez faire quelque chose comme ceci, en utilisant des opérateurs et des méthodes.
Point<double> d = new Point<double>() { X = 10d, Y = 10d, Z = 10d }; Point<int> i = (Point<int>) d; Point<float> f = (Point<float>) i; d = (Point<double>) f;
Utilisation:
public class Point<T> { public T X { get; set; } public T Y { get; set; } public T Z { get; set; } public static explicit operator Point<T>(Point<int> v) { return v.As<T>(); } public static explicit operator Point<T>(Point<double> v) { return v.As<T>(); } public static explicit operator Point<T>(Point<float> v) { return v.As<T>(); } public Point<TU> As<TU>() { return new Point<TU>() { X = ConvertTo<TU>(X), Y = ConvertTo<TU>(Y), Z = ConvertTo<TU>(Z) }; } private static TU ConvertTo<TU>(T t) { return (TU) Convert.ChangeType(t, typeof(TU)); } }
Les opérateurs de conversion ne peuvent pas être génériques.
Avez-vous besoin de
U
comme paramètre générique? Vous pouvez remplacerU
par un type réel. Certes, vous ne disposez que d'un nombre limité de types de conversion que vous pouvez effectuer.