3
votes

Casting explicite d'un générique vers un autre type en C #

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>


2 commentaires

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 remplacer U par un type réel. Certes, vous ne disposez que d'un nombre limité de types de conversion que vous pouvez effectuer.


5 Réponses :


2
votes

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.


3 commentaires

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.



3
votes

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));
}


2 commentaires

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!



2
votes

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.


2 commentaires

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.



1
votes

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 qui sera ensuite lancé lors de l'exécution lors d'une tentative de conversion.


0 commentaires

1
votes

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));
    }
}


0 commentaires