7 votes

Aide avec les opérateurs mathématiques en classe (c#)

public class Racional
{
    private T nominator;
    private T denominator;
    public T Nominator
    {
        get { return nominator; }
        set { nominator = value; }
    }
    public T Denominator
    {
        get { return denominator; }
        set { denominator = value; }
    }
    public Racional(T nominator, T denominator)
    {
        this.nominator = nominator;
        this.denominator = denominator;
    }
    public static Racional operator *(Racional a, Racional b)
    {
        return new Racional(Convert.ToInt32(a.Nominator) * Convert.ToInt32(b.Nominator), Convert.ToInt32(a.Denominator) * Convert.ToInt32(b.Denominator));
    }
    public override string ToString()
    {
        return "(" + this.nominator + " " + this.denominator + ")";
    }
}

Je suis intéressé par cette partie :

public static Racional operator *(Racional a, Racional b)
{
    return new Racional(Convert.ToInt32(a.Nominator) * Convert.ToInt32(b.Nominator), Convert.ToInt32(a.Denominator) * Convert.ToInt32(b.Denominator));
}

Quel est le problème :

Un des paramètres d'un opérateur binaire doit être le type contenant

Comment puis-je normalement coder cette partie pour les opérations mathématiques ?

3voto

Ani Points 59747

La raison pour laquelle votre code ne compile pas est expliquée par l'erreur du compilateur. Le type conteneur est une définition de type générique, et un type générique construit à partir de ce type n'est pas considéré comme étant le même type.

J'ai quelques questions :

  1. Pourquoi le type Rational doit-il être générique ? Un nombre rationnel est défini comme un nombre qui peut être exprimé comme le quotient / la fraction de deux entiers (où le dénominateur n'est pas 0). Pourquoi ne pas rendre le type non-générique et simplement utiliser int partout ? Ou avez-vous l'intention que le type soit utilisé pour d'autres types intégraux tels que long et BigInteger ? Dans ce cas, envisagez d'utiliser quelque chose comme la suggestion d'Aliostad si vous voulez un mécanisme de partage de code.
  2. Pourquoi voulez-vous que le produit de deux nombres rationnels soit égal à la somme de leurs numérateurs sur la somme de leurs dénominateurs ? Cela n'a pas de sens pour moi.

En tout cas, il semble que vous vouliez être capable d'ajouter de manière 'générique' deux instances d'un type 'addable'. Malheureusement, il n'y a actuellement aucun moyen d'exprimer une contrainte de type 'possède un opérateur d'addition approprié' en C#.

Méthode n°1 : Une solution de contournement pour cela en C# 4 est d'utiliser le type dynamic pour vous donner les sémantiques d'"opérateur virtuel" désirées.

public static Racional operator *(Racional a, Racional b)
{
    var sommeNumérateur = (dynamic)a.Numérateur + b.Numérateur;
    var sommeDénominateur = (dynamic)a.Dénominateur + b.Dénominateur;

    return new Racional(sommeNumérateur, sommeDénominateur);
}

L'opérateur va générer une erreur si le type n'a pas d'opérateur d'addition approprié.


Méthode n°2 : Une autre (plus efficace) méthode est d'utiliser des expressions d'arbre.

Tout d'abord, créez et mettez en cache un délégué qui peut effectuer l'addition en compilant l'expression appropriée :

private readonly static Func Adder;

static Racional()
{
    var premierOpérande = Expression.Parameter(typeof(T), "x");
    var deuxièmeOpérande = Expression.Parameter(typeof(T), "y");
    var corps = Expression.Add(premierOpérande, deuxièmeOpérande);
    Adder = Expression.Lambda>
                (corps, premierOpérande, deuxièmeOpérande).Compile();    
} 

(Le constructeur statique va générer une erreur si le type n'a pas d'opérateur d'addition approprié.)

Ensuite, utilisez-le dans l'opérateur :

public static Racional operator *(Racional a, Racional b)
{
    var sommeNumérateur = Adder(a.Numérateur, b.Numérateur);
    var sommeDénominateur = Adder(a.Dénominateur, b.Dénominateur);
    return new Racional(sommeNumérateur, sommeDénominateur);
}

2voto

Aliostad Points 47792

Le problème ici est que vous définissez un opérateur pour Racional dans la classe Racional. Ce n'est pas possible. Les types ne sont pas les mêmes, vous ne pouvez définir un opérateur que pour Racional.

Les génériques ne peuvent pas exprimer la généralisation des opérateurs car ils sont définis uniquement pour certains types. La solution est de créer une classe et d'hériter de Racional:

public class IntRacional : Racional
{
    public static Racional operator +(IntRacional a, IntRacional b)
    {
        return new Racional()
        {
            Numerateur = a.Numerateur + b.Numerateur,
            Denominateur = a.Denominateur + b.Denominateur
        };
    }
}

1voto

Doc Brown Points 13438

Pour résoudre votre problème, vous devez fournir des fonctions de conversion de T vers un type où operator+ est défini et vice versa. En supposant que Int64 est assez grand dans la plupart des cas, cela peut être fait de cette manière:

public class Racional 
{
    private T nominator;
    private T denominator;
    static Converter T_to_Int64;
    static Converter Int64_to_T;

    public static void InitConverters(Converter t2int, Converter int2t )
    {
        T_to_Int64 = t2int;
        Int64_to_T = int2t;
    }

    public T Nominator
    {
        get { return nominator; }
        set { nominator = value; }
    }
    public T Denominator
    {
        get { return denominator; }
        set { denominator = value; }
    }
    public Racional(T nominator, T denominator)
    {
        this.nominator = nominator;
        this.denominator = denominator;
    }
    public static Racional operator *(Racional a, Racional b) 
    {
        return new Racional(
            Int64_to_T(T_to_Int64(a.nominator) + T_to_Int64(b.nominator)),
            Int64_to_T(T_to_Int64(a.denominator) + T_to_Int64(b.denominator)));
    }

    // By the way, should this not be * instead of + ???
    //
    // public static Racional operator *(Racional a, Racional b) 
    // {
    //    return new Racional(
    //        Int64_to_T(T_to_Int64(a.nominator) * T_to_Int64(b.nominator)),
    //        Int64_to_T(T_to_Int64(a.denominator) * T_to_Int64(b.denominator)));
    // }

    public override string ToString()
    {
        return "(" + this.nominator + " " + this.denominator + ")";
    }
}

Bien sûr, cela a l'inconvénient que vous devez fournir l'initialisation de ces convertisseurs quelque part au début du programme, cela devrait ressembler à ceci:

Racional.InitConverters(x => (Int64)x, y => (int)y);

Dans un programme réel, vous pouvez savoir quelles substitutions possibles pour T vous allez utiliser. Vous pouvez donc fournir ces 3 ou 4 appels dans un constructeur statique comme ceci:

    public static Racional()
    {
        Racional.InitConverters(x => (Int64)x, y => (int)y);
        Racional.InitConverters(x => (Int64)x, y => (short)y);
        Racional.InitConverters(x => (Int64)x, y => (Int64)y);
    }

devrait être suffisant dans la plupart des cas. Notez que cette initialisation de convertisseur est répétée pour les 3 types 3 fois encore, réinitialisant les fonctions de conversion plusieurs fois encore. En pratique, cela ne devrait poser aucun problème.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X