Overload degli operatori (F#)

In questo argomento viene descritto come eseguire l'overload di operatori aritmetici in un tipo di record o classe e a livello globale.

// Overloading an operator as a class or record member. 
static member (operator-symbols) (parameter-list) = 
    method-body
// Overloading an operator at the global level
let [inline] (operator-symbols) parameter-list =
    function-body

Note

Nella sintassi precedente operator-symbol è uno tra +, -, *, /, = e così via. parameter-list specifica gli operandi nell'ordine con cui appaiono nella sintassi utilizzata in genere per l'operatore. L'elemento method-body costruisce il valore risultante.

Gli overload degli operatori per gli operatori devono essere statici. Gli overload degli operatori per gli operatori unari, ad esempio + e -, devono includere una tilde (~) in operator-symbol per indicare che l'operatore è unario e non binario, come indicato nella dichiarazione seguente:

static member (~-) (v : Vector)

Nel codice seguente viene illustrata una classe di vettori con solo due operatori, uno per la negazione unaria e uno per la moltiplicazione per un valore scalare. Nell'esempio sono necessari due overload per la moltiplicazione scalare in quanto l'operatore deve essere utilizzato indipendentemente dall'ordine con cui appaiono il vettore e il valore scalare.

type Vector(x: float, y : float) =
   member this.x = x
   member this.y = y
   static member (~-) (v : Vector) =
     Vector(-1.0 * v.x, -1.0 * v.y)
   static member (*) (v : Vector, a) =
     Vector(a * v.x, a * v.y)
   static member (*) (a, v: Vector) =
     Vector(a * v.x, a * v.y)
   override this.ToString() =
     this.x.ToString() + " " + this.y.ToString()

let v1 = Vector(1.0, 2.0)

let v2 = v1 * 2.0
let v3 = 2.0 * v1

let v4 = - v2

printfn "%s" (v1.ToString())
printfn "%s" (v2.ToString())
printfn "%s" (v3.ToString())
printfn "%s" (v4.ToString())

Creazione di nuovi operatori

È possibile eseguire l'overload di tutti gli operatori standard, ma è anche possibile creare nuovi operatori da sequenze di caratteri specifici. I caratteri consentiti sono !, %, &, *, +, -, ., /, <, =, >, ?, @, ^, |, e ~. Il carattere ~ ha il significato speciale di rendere unario un operatore e non fa parte della sequenza di caratteri dell'operatore. Non tutti gli operatori possono essere resi unari, come descritto in Operatori di prefisso e infisso più avanti in questo argomento.

A seconda della sequenza di caratteri esatta che si utilizza, l'operatore avrà determinati valori di precedenza e associatività. L'associatività può essere da sinistra verso destra o da destra verso sinistra e può essere utilizzata ogni volta che operatori con lo stesso livello di precedenza appaiono in sequenza senza parentesi.

Il carattere operatore . non influisce sulla precedenza, pertanto, se ad esempio si desidera definire una versione di moltiplicazione con precedenza e associatività uguali a quelle della moltiplicazione ordinaria, è possibile creare operatori quali .*.

In Riferimenti per simboli e operatori (F#) è disponibile una tabella in cui è illustrata la precedenza di tutti gli operatori in F#.

Nomi di operatori di overload

Se il compilatore F# compila un'espressione di operatore, viene generato un metodo con un nome generato dal compilatore per tale operatore. Si tratta del nome visualizzato in Microsoft Intermediate Language (MSIL) per il metodo e anche nella reflection e in IntelliSense. In genere, non è necessario utilizzare questi nomi nel codice F#.

Nella tabella seguente sono illustrati gli operatori standard e i relativi nomi generati.

Operatore

Nome generato

[]

op_Nil

::

op_Cons

+

op_Addition

-

op_Subtraction

*

op_Multiply

/

op_Division

@

op_Append

^

op_Concatenate

%

op_Modulus

&&&

op_BitwiseAnd

|||

op_BitwiseOr

^^^

op_ExclusiveOr

<<<

op_LeftShift

~~~

op_LogicalNot

>>>

op_RightShift

~+

op_UnaryPlus

~-

op_UnaryNegation

=

op_Equality

<=

op_LessThanOrEqual

>=

op_GreaterThanOrEqual

<

op_LessThan

>

op_GreaterThan

?

op_Dynamic

?<-

op_DynamicAssignment

|>

op_PipeRight

<|

op_PipeLeft

!

op_Dereference

>>

op_ComposeRight

<<

op_ComposeLeft

<@ @>

op_Quotation

<@@ @@>

op_QuotationUntyped

+=

op_AdditionAssignment

-=

op_SubtractionAssignment

*=

op_MultiplyAssignment

/=

op_DivisionAssignment

..

op_Range

.. ..

op_RangeStep

Altre combinazioni di caratteri dell'operatore non elencate possono essere utilizzate come operatori e possono avere nomi costituiti concatenando i nomi per i singoli caratteri indicati nella tabella seguente. Ad esempio, +! diventa op_PlusBang.

Caratteri dell'operatore

Nome

>

Greater

<

Less

+

Plus

-

Minus

*

Multiply

/

Divide

=

Equals

~

Twiddle

%

Percent

.

Dot

&

Amp

|

Bar

@

At

^

Hat

!

Bang

?

Qmark

(

LParen

,

Comma

)

RParen

[

LBrack

]

RBrack

Operatori di infisso e di prefisso

Gli operatori prefisso devono essere posizionati di fronte a uno o più operandi, in modo molto analogo a una funzione. Gli operatori infisso devono essere posizionati tra i due operandi.

Solo gli operatori sicuri possono essere utilizzati come operatori prefisso. Alcuni operatori sono sempre operatori prefisso, altri possono essere infisso o prefisso e il resto è sempre operatori infisso. Gli operatori ! e ~, o le sequenze ripetute di questi, sono sempre operatori prefisso. Gli operatori +, -, +., -., &, &&, % e %% possono essere operatori prefisso o operatori infissi. È possibile distinguere la versione di prefisso di questi operatori dalla versione di infisso aggiungendo ~ all'inizio di un operatore di prefisso durante la sua definizione. Il simbolo ~ non viene utilizzato quando si utilizza l'operatore, ma solo quando lo si definisce.

Esempio

Nel codice seguente viene illustrato l'utilizzo dell'overload degli operatori per implementare un tipo di frazione. Una frazione è rappresentata da un numeratore e un denominatore. La funzione hcf viene utilizzata per determinare il fattore comune più elevato utilizzato per ridurre le frazioni.

// Determine the highest common factor between
// two positive integers, a helper for reducing
// fractions.
let rec hcf a b =
  if a = 0u then b
  elif a<b then hcf a (b - a)
  else hcf (a - b) b

// type Fraction: represents a positive fraction
// (positive rational number).
type Fraction =
   {
      // n: Numerator of fraction.
      n : uint32
      // d: Denominator of fraction.
      d : uint32
   }

   // Produce a string representation. If the
   // denominator is "1", do not display it.
   override this.ToString() =
      if (this.d = 1u)
        then this.n.ToString()
        else this.n.ToString() + "/" + this.d.ToString()

   // Add two fractions.
   static member (+) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.d + f2.n * f1.d
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Adds a fraction and a positive integer.
   static member (+) (f1: Fraction, i : uint32) =
      let nTemp = f1.n + i * f1.d
      let dTemp = f1.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Adds a positive integer and a fraction.
   static member (+) (i : uint32, f2: Fraction) =
      let nTemp = f2.n + i * f2.d
      let dTemp = f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Subtract one fraction from another.
   static member (-) (f1 : Fraction, f2 : Fraction) =
      if (f2.n * f1.d > f1.n * f2.d)
        then failwith "This operation results in a negative number, which is not supported."
      let nTemp = f1.n * f2.d - f2.n * f1.d
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Multiply two fractions.
   static member (*) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.n
      let dTemp = f1.d * f2.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // Divide two fractions.
   static member (/) (f1 : Fraction, f2 : Fraction) =
      let nTemp = f1.n * f2.d
      let dTemp = f2.n * f1.d
      let hcfTemp = hcf nTemp dTemp
      { n = nTemp / hcfTemp; d = dTemp / hcfTemp }

   // A full set of operators can be quite lengthy. For example,
   // consider operators that support other integral data types,
   // with fractions, on the left side and the right side for each.
   // Also consider implementing unary operators.

let fraction1 = { n = 3u; d = 4u }
let fraction2 = { n = 1u; d = 2u }
let result1 = fraction1 + fraction2
let result2 = fraction1 - fraction2
let result3 = fraction1 * fraction2
let result4 = fraction1 / fraction2
let result5 = fraction1 + 1u
printfn "%s + %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result1.ToString())
printfn "%s - %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result2.ToString())
printfn "%s * %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result3.ToString())
printfn "%s / %s = %s" (fraction1.ToString()) (fraction2.ToString()) (result4.ToString())
printfn "%s + 1 = %s" (fraction1.ToString()) (result5.ToString())
  

Operatori a livello globale

È anche possibile definire gli operatori a livello globale. Il codice seguente definisce un operatore +?.

let inline (+?) (x: int) (y: int) = x + 2*y
printf "%d" (10 +? 1)

L'output del codice indicato in precedenza è 12.

È possibile ridefinire gli operatori aritmetici regolari in questo modo, in quanto le regole di ambito per F# prevedono che i nuovi operatori definiti abbiano la precedenza sugli operatori incorporati.

La parola chiave inline viene spesso utilizzata con gli operatori globali, che sono spesso piccole funzioni che si integrano meglio nel codice chiamante. L'impostazione delle funzioni degli operatori come inline ne consente inoltre l'utilizzo con parametri di tipo risolti in modo statico, per produrre codice generico risolto in modo statico. Per ulteriori informazioni, vedere Funzioni inline (F#) e Parametri di tipo risolti staticamente (F#).

Vedere anche

Concetti

Membri (F#)