Quale implementazione usi quando lavori con gli operatori di .Net?

2

La mia domanda è in un certo senso un seguito di questo post su Time Series in .Net.

Idealmente, ti piacerebbe espandere la classe base TimeSeries<T> di qualcosa come NumericTimeSeries<T> , dove vorresti assicurarti che il tipo T implementa gli operatori numerici comuni + , - , * , '/', ...

Non sono sicuro di quale sarebbe il modo più intelligente per farlo.

La mia prima idea è di creare una classe astratta Number , con metodi astratti Add(Number n1,Number n2) , Multiply(Number n1,Number n2) , Subtract(Number n1,Number n2) , Divide(Number n1,Number n2) (e così via). Chiamerei quindi questi metodi negli overload dell'operatore.

Posso quindi creare classi concrete come IntNumber , DoubleNumber , MatrixNumber , ComplexNumber (e così via).

Il problema è che nell'implementazione dei metodi, dovrei creare un terribile switch per determinare quale sia la classe di ogni parametro, e non è molto elegante.

Lo avresti fatto allo stesso modo?

È già stato fatto qualcosa?

In F # vorrei utilizzare e funzioni inline o matching pattern utilizzando unioni discriminate . Ma cosa dovrei fare in C #?

Modifica :

C'è stata una splendida risposta di Olivier Jacot-Descombes. Tuttavia, vorrei specificare una funzione.

I numeri possono applicare l'operatore tra di loro (per esempio puoi moltiplicare una matrice con uno scalare). Il problema con questo è che non puoi sapere in anticipo il tipo del risultato; moltiplicando un vettore (trasposto) e un vettore restituisce un doppio ...

    
posta SRKX 30.12.2011 - 00:20
fonte

3 risposte

4

È possibile farlo con alcuni trucchi. Per prima cosa, definiamo una classe Number astratta con un parametro generico T e il divertente parametro generico vincolo where T : Number<T> . Potrebbe sembrare una ricorsione senza fine, ma in realtà significa solo che T deve derivare da Number<T> . Abbiamo bisogno del parametro generico per il trucco implicito di conversione:

public abstract class Number<T>
    where T : Number<T>
{
    public static Number<T> operator +(Number<T> x, Number<T> y)
    {
        return x.Sum(y);
    }

    // Automatic conversion from Number<T> to T
    public static implicit operator T(Number<T> x)
    {
        return (T)x;
    }

    // Each derived type must supply its own implementation
    internal abstract Number<T> Sum(T x);
}

Number definisce anche un sovraccarico per l'operatore "+". Poiché questo operatore è statico, non è possibile sovrascriverlo nelle classi derivate. Pertanto dichiariamo un metodo Sum astratto, che l'operatore "+" utilizza per calcolare la sua somma. Implementiamo anche una conversione implicita da Number<T> a T .

Ora implementiamo una classe per i numeri interi che deriva da Number<T> :

public class IntNumber : Number<IntNumber>
{
    private int _n;

    public IntNumber(int n)
    {
        _n = n;
    }

    public int Value { get { return _n; } }

    // Automatic conversion from int to IntNumber
    public static implicit operator IntNumber(int x)
    {
        return new IntNumber(x);
    }

    internal override Number<IntNumber> Sum(IntNumber x)
    {
        return new IntNumber(_n + x._n);
    }

    public override string ToString()
    {
        return _n.ToString();
    }
}

La parte importante è l'implementazione del metodo Sum . La conversione implicita da int a IntNumber è facoltativa. Sostituendo il metodo ToString è più semplice visualizzare IntNumber . Dichiariamo anche un costruttore e una proprietà Value che restituisce il numero intero.

Possiamo fare lo stesso con i numeri complessi:

public class ComplexNumber : Number<ComplexNumber>
{
    private double _re, _im;

    public ComplexNumber(double re, double im)
    {
        _re = re;
        _im = im;
    }

    public double Re { get { return _re; } }
    public double Im { get { return _im; } }

    public static ComplexNumber operator +(ComplexNumber x, ComplexNumber y)
    {
        return x.Sum(y);
    }

    // Automatic conversion from double to ComplexNumber
    public static implicit operator ComplexNumber(double x)
    {
        return new ComplexNumber(x, 0);
    }

    // Automatic conversion from IntNumber to ComplexNumber
    public static implicit operator ComplexNumber(IntNumber x)
    {
        return new ComplexNumber(x.Value, 0);
    }

    internal override Number<ComplexNumber> Sum(ComplexNumber x)
    {
        return new ComplexNumber(_re + x._re, _im + x._im);
    }

    public override string ToString()
    {
        return "(" + _re + "+" + _im + "i)";
    }
}

Il sovraccarico dell'operatore nella classe base è necessario per gestire numeri generici digitati (vedi Calcolatrice), quello della classe derivata è richiesto, poiché la conversione automatica da IntNumber a ComplexNumber funziona solo con questo .

Ora dichiariamo un calcolatore generico che opera su uno qualsiasi dei nostri tipi di numeri. (È solo una semplice sostituzione di NumericTimeSeries a scopo di test).

public class Calculator<T>
    where T : Number<T>
{
    public T GetSum(T x, T y)
    {
        return x + y; // <== You can add any numbers with generic type T with "+"
    }
}

Si noti che possiamo semplicemente aggiungere due numeri di un tipo generico con l'operatore "+". A causa dell'operatore implicito dichiarato in Number<T> non abbiamo bisogno di alcun cast!

Cerchiamo di testare l'aggiunta di tipi di numeri generici con il calcolatore generico e l'aggiunta diretta di diversi tipi di numeri:

IntNumber i1 = new IntNumber(2);
IntNumber i2 = new IntNumber(3);
var intCalculator = new Calculator<IntNumber>();
Console.WriteLine(intCalculator.GetSum(i1, i2)); // ==> 5
Console.WriteLine(intCalculator.GetSum(4, 6)); // ==> 10

ComplexNumber c1 = new ComplexNumber(2, 7);
ComplexNumber c2 = new ComplexNumber(3, -1);
var complexCalculator = new Calculator<ComplexNumber>();
Console.WriteLine(complexCalculator.GetSum(c1, c2)); // ==> (5+6i)

Console.WriteLine(c1 + 100.5); // ==> (102.5+7i)
Console.WriteLine(c1 + i1); // ==> (4+7i)
Console.WriteLine(i1 + c1); // ==> (4+7i)
    
risposta data 30.12.2011 - 17:18
fonte
1

L'overloading dell'operatore in .Net è stupefacente, e direi che in questo caso è più sforzo del suo valore, non mi preoccuperei di farlo. Se non vuoi fare un gruppo di controlli null (e chi lo fa) -

return Object.ReferenceEquals(null, leftIntNum) ? //because someone could have always overloaded == and done it wrong
null : 
leftIntNum.MultiplyBy(rightIntNum)'

È possibile utilizzare i metodi di estensione

  public static NullMultiplyBy<T1, T2>(this T1 numerator, T1 denominator)
   where T1 : Number<T2> {
    return Object.ReferenceEquals(null, numerator) ? 
    null : 
    numerator.MultiplyBy(rightIntNum)'    
  }

Oltre a questo, esporre un'interfaccia, non una classe astratta. Puoi ancora utilizzare una classe base astratta, ma evita di farne riferimento nelle firme dei metodi.

    
risposta data 30.12.2011 - 01:24
fonte
-1

Il mio modo di affrontare questo è quello di scappare urlando dalla stanza.

No, non sto scherzando, esattamente. Ho visto persone provare a utilizzare l'overloading dell'operatore in diversi progetti. In ognuno di questi progetti, arrivò un punto in cui i programmatori persero giorni, come in "più giorni interi", cercando di capire un problema che risultò essere causato dall'operatore sovraccarico "sbagliato" che veniva chiamato in una situazione in cui non era previsto. Questo è in aggiunta alle ore necessarie per impostare tutte le conversioni e le versioni degli operatori in modo che PENSI che funzioni.

"MyNumber.Add (otherNumber)" sembra un po 'goffo, ma è leggibile . Sai cosa sta succedendo e quale codice viene chiamato. "MyNumber + otherNumber" potrebbe essere una mezza dozzina di cose diverse e non hai idea di quale sia fino a quando non entri nell'assemblea.

Non utilizzare mai l'overloading dell'operatore.

    
risposta data 01.01.2012 - 00:07
fonte

Leggi altre domande sui tag