Perché dobbiamo definire sia == che! = In C #?


346

Il compilatore C # richiede che ogni volta che un tipo personalizzato definisce l'operatore ==, deve anche definire !=(vedi qui ).

Perché?

Sono curioso di sapere perché i progettisti lo hanno ritenuto necessario e perché il compilatore non può portare a una ragionevole implementazione per uno degli operatori quando è presente solo l'altro. Ad esempio, Lua ti consente di definire solo l'operatore di uguaglianza e ottieni l'altro gratuitamente. C # potrebbe fare lo stesso chiedendoti di definire == o entrambi == e! = E quindi compilare automaticamente l'operatore mancante! = Come !(left == right).

Capisco che ci sono strani casi angolari in cui alcune entità potrebbero non essere né uguali né disuguali (come quelle di IEEE-754 NaN), ma quelle sembrano un'eccezione, non la regola. Quindi questo non spiega perché i progettisti del compilatore C # abbiano fatto dell'eccezione la regola.

Ho visto casi di scarsa fattura in cui è definito l'operatore di uguaglianza, quindi l'operatore di disuguaglianza è un copia-incolla con ogni confronto invertito e ogni && è passato a un || (ottieni il punto ... in sostanza! (a == b) espanso attraverso le regole di De Morgan). È una cattiva pratica che il compilatore potrebbe eliminare in base alla progettazione, come nel caso di Lua.

Nota: lo stesso vale per gli operatori <> <=> =. Non riesco a immaginare casi in cui dovrai definirli in modi innaturali. Lua ti permette di definire solo <e <= e definisce> = e> naturalmente attraverso la negazione dei formatori. Perché C # non fa lo stesso (almeno "di default")?

MODIFICARE

Apparentemente ci sono validi motivi per consentire al programmatore di implementare controlli per l'uguaglianza e la disuguaglianza come preferiscono. Alcune delle risposte indicano casi in cui potrebbe essere carino.

Il kernel della mia domanda, tuttavia, è perché questo è forzatamente richiesto in C # quando di solito non è logicamente necessario?

È anche in netto contrasto con le scelte progettuali per interfacce .NET come Object.Equals, in IEquatable.Equals IEqualityComparer.Equalscui la mancanza di una NotEqualscontroparte mostra che il framework considera gli !Equals()oggetti ineguali e basta. Inoltre, classi come Dictionarye metodi come .Contains()dipendono esclusivamente dalle interfacce sopra menzionate e non usano direttamente gli operatori anche se sono definiti. In effetti, quando ReSharper genera membri di uguaglianza, definisce entrambi ==e !=in termini Equals()e anche solo se l'utente sceglie di generare operatori. Gli operatori di uguaglianza non sono richiesti dal framework per comprendere l'uguaglianza degli oggetti.

Fondamentalmente, il framework .NET non si preoccupa di questi operatori, si preoccupa solo di alcuni Equalsmetodi. La decisione di richiedere che entrambi gli operatori == e! = Siano definiti in tandem dall'utente è strettamente legata alla progettazione del linguaggio e non alla semantica degli oggetti per quanto riguarda .NET.


24
+1 per una domanda eccellente. Non sembra esserci alcun motivo ... ovviamente il compilatore potrebbe presumere che a! = B possa essere tradotto in! (A == b) a meno che non sia detto diversamente. Sono curioso di sapere perché anche loro hanno deciso di farlo.
Patrick87

16
Questo è il più veloce in cui ho visto una domanda votata e favorita.
Christopher Currens,

26
Sono sicuro che anche @Eric Lippert può fornire una risposta valida.
Yuck

17
"Lo scienziato non è una persona che dà le risposte giuste, è una persona che pone le domande giuste". Questo è il motivo per cui in SE le domande sono incoraggiate. E funziona!
Adriano Carneiro,

4
La stessa domanda per Python: stackoverflow.com/questions/4969629/…
Josh Lee

Risposte:


161

Non posso parlare per i progettisti del linguaggio, ma da quello su cui posso ragionare, sembra che sia stata una decisione intenzionale e corretta sul design.

Guardando questo codice F # di base, puoi compilarlo in una libreria funzionante. Questo è il codice legale per F # e sovraccarica solo l'operatore di uguaglianza, non la disuguaglianza:

module Module1

type Foo() =
    let mutable myInternalValue = 0
    member this.Prop
        with get () = myInternalValue
        and set (value) = myInternalValue <- value

    static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
    //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop

Questo fa esattamente quello che sembra. Crea solo un comparatore di uguaglianza ==e verifica se i valori interni della classe sono uguali.

Sebbene non sia possibile creare una classe come questa in C #, è possibile utilizzarne una compilata per .NET. È ovvio che utilizzerà il nostro operatore sovraccarico per == So, a cosa serve il runtime !=?

Lo standard C # EMCA ha un sacco di regole (sezione 14.9) che spiegano come determinare quale operatore utilizzare per valutare l'uguaglianza. Per dirlo eccessivamente semplificato e quindi non perfettamente accurato, se i tipi che vengono confrontati sono dello stesso tipo e c'è un operatore di uguaglianza sovraccarico presente, userà quel sovraccarico e non l'operatore di uguaglianza di riferimento standard ereditato da Object. Non sorprende quindi che, se è presente solo uno degli operatori, utilizzerà l'operatore di uguaglianza di riferimento predefinito, che tutti gli oggetti hanno, non vi è un sovraccarico per esso. 1

Sapendo che è così, la vera domanda è: perché è stato progettato in questo modo e perché il compilatore non lo capisce da solo? Molte persone dicono che questa non è stata una decisione di progettazione, ma mi piace pensare che sia stata pensata in questo modo, soprattutto per quanto riguarda il fatto che tutti gli oggetti hanno un operatore di uguaglianza predefinito.

Quindi, perché il compilatore non crea automagicamente l' !=operatore? Non posso esserne sicuro, a meno che qualcuno di Microsoft non lo confermi, ma questo è ciò che posso determinare basandomi sui fatti.


Per prevenire comportamenti imprevisti

Forse voglio fare un confronto di valore ==per testare l'uguaglianza. Tuttavia, quando si trattava di !=me non mi importava affatto se i valori fossero uguali a meno che il riferimento fosse uguale, perché per il mio programma di considerarli uguali, mi importa solo se i riferimenti corrispondono. Dopotutto, questo è effettivamente definito come comportamento predefinito del C # (se entrambi gli operatori non fossero sovraccarichi, come nel caso di alcune librerie .net scritte in un'altra lingua). Se il compilatore aggiungesse automaticamente il codice, non potevo più fare affidamento sul compilatore per ottenere il codice di output che dovrebbe essere conforme. Il compilatore non dovrebbe scrivere codice nascosto che modifica il comportamento del tuo, soprattutto quando il codice che hai scritto rientra negli standard sia di C # che della CLI.

In termini di ciò che ti costringe a sovraccaricarlo, invece di passare al comportamento predefinito, posso solo affermare con fermezza che è nello standard (EMCA-334 17.9.2) 2 . Lo standard non specifica il perché. Credo che ciò sia dovuto al fatto che C # prende in prestito molti comportamenti dal C ++. Vedi sotto per ulteriori informazioni al riguardo.


Quando si esegue l' override !=e ==non è necessario restituire bool.

Questa è un'altra probabile ragione. In C #, questa funzione:

public static int operator ==(MyClass a, MyClass b) { return 0; }

è valido come questo:

public static bool operator ==(MyClass a, MyClass b) { return true; }

Se stai restituendo qualcosa di diverso da bool, il compilatore non può inferire automaticamente un tipo opposto. Inoltre, nel caso in cui l'operatore fa bool ritorno, semplicemente non ha senso per loro creano generare il codice che esisterebbe solo in quel caso uno specifico o, come ho detto sopra, il codice che nasconde il comportamento predefinito del CLR.


C # prende in prestito molto da C ++ 3

Quando è stato introdotto C #, c'era un articolo nella rivista MSDN che scriveva, parlando di C #:

Molti sviluppatori desiderano che esistesse un linguaggio facile da scrivere, leggere e mantenere come Visual Basic, ma che fornisse comunque la potenza e la flessibilità del C ++.

Sì, l'obiettivo di progettazione per C # era quello di fornire quasi la stessa quantità di energia del C ++, sacrificando solo un po 'per comodità come la rigida sicurezza dei tipi e la raccolta dei rifiuti. C # è stato fortemente modellato su C ++.

Potresti non essere sorpreso di apprendere che in C ++ gli operatori di uguaglianza non devono restituire bool , come mostrato in questo programma di esempio

Ora, C ++ non richiede direttamente di sovraccaricare l'operatore complementare. Se hai compilato il codice nel programma di esempio, vedrai che viene eseguito senza errori. Tuttavia, se hai provato ad aggiungere la riga:

cout << (a != b);

otterrete

errore del compilatore C2678 (MSVC): binario '! =': nessun operatore trovato che accetta un operando di sinistra di tipo 'Test' (o non c'è conversione accettabile) `.

Quindi, sebbene C ++ stesso non richieda il sovraccarico in coppia, non ti permetterà di usare un operatore di uguaglianza che non hai sovraccaricato su una classe personalizzata. È valido in .NET, perché tutti gli oggetti ne hanno uno predefinito; C ++ no.


1. Come nota a margine, lo standard C # richiede comunque di sovraccaricare la coppia di operatori se si desidera sovraccaricare uno dei due. Questa è una parte dello standard e non semplicemente il compilatore . Tuttavia, si applicano le stesse regole relative alla determinazione dell'operatore da chiamare quando si accede a una libreria .net scritta in un'altra lingua che non ha gli stessi requisiti.

2. EMCA-334 (pdf) ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf )

3. E Java, ma non è questo il punto


75
" non devi tornare bool " .. Penso che sia la nostra risposta proprio lì; molto bene. Se! (O1 == o2) non è nemmeno ben definito, allora non puoi aspettarti che lo standard si basi su di esso.
Brian Gordon,

10
Ironia della sorte in un argomento sugli operatori logici hai usato un doppio negativo nella frase, "C # non ti consente di non sovrascrivere solo uno dei due", il che in realtà significa logicamente "C # ti consente di sovrascrivere solo uno dei due".
Dan Diplo,

13
@Dan Diplo, Va bene, non è come se non mi fosse proibito non farlo o no.
Christopher Currens,

6
Se vero, sospetto che il numero 2 sia corretto; ma ora la domanda diventa, perché consentire ==di restituire altro che a bool? Se si restituisce un int, non è più possibile utilizzarlo come un'istruzione condizionale, ad es. if(a == b)non verrà più compilato. Qual e il punto?
BlueRaja - Danny Pflughoeft il

2
Potresti restituire un oggetto che ha una conversione implicita in bool (operatore vero / falso) ma non riesco ancora a vedere dove sarebbe utile.
Stefan Dragnev,

53

Probabilmente se qualcuno ha bisogno di implementare una logica a tre valori (cioè null). In casi del genere, ad esempio SQL standard ANSI, gli operatori non possono essere semplicemente negati a seconda dell'input.

Potresti avere un caso in cui:

var a = SomeObject();

E a == trueritorna falsee a == falseritorna anche false.


1
Direi che in quel caso, dato che anon è un booleano, dovresti confrontarlo con un enum a tre stati, non un vero trueo false.
Brian Gordon,

18
A proposito, non posso credere che nessuno abbia fatto riferimento alla battuta di FileNotFound ..
Brian Gordon,

28
Se a == trueè falsecosì mi aspetterei sempre a != truedi essere true, nonostante a == falsesiafalse
Fede

20
@Fede ha colpito l'unghia sulla testa. Questo non ha davvero nulla a che fare con la domanda : stai spiegando perché qualcuno potrebbe voler scavalcare ==, non perché dovrebbero essere costretti a scavalcare entrambi.
BlueRaja - Danny Pflughoeft il

6
@Yuck - Sì, c'è una buona implementazione predefinita: è il caso comune. Anche nel tuo esempio, l'implementazione predefinita di !=sarebbe corretta. Nel caso estremamente raro in cui vorremmo x == yed x != yentrambi essere falsi (non riesco a pensare a un singolo esempio che abbia un senso) , potrebbero comunque ignorare l'implementazione predefinita e fornire la propria. Rendi sempre il caso comune predefinito!
BlueRaja - Danny Pflughoeft il

25

A parte questo, il C # si discosta dal C ++ in molte aree, la migliore spiegazione a cui riesco a pensare è che in alcuni casi potresti voler adottare un approccio leggermente diverso per dimostrare "non uguaglianza" piuttosto che dimostrare "uguaglianza".

Ovviamente con il confronto delle stringhe, ad esempio, puoi semplicemente verificare l'uguaglianza e returnfuori dal ciclo quando vedi caratteri non corrispondenti. Tuttavia, potrebbe non essere così pulito con problemi più complicati. Il filtro di fioritura viene in mente; è molto facile dire rapidamente se l'elemento non è nel set, ma è difficile dire se l'elemento è nel set. Mentre la stessa returntecnica potrebbe essere applicata, il codice potrebbe non essere così carino.


6
Grande esempio; Penso che tu sia sul motivo per cui. Un semplice !ti blocca in una singola definizione per l'uguaglianza, in cui un programmatore informato potrebbe essere in grado di ottimizzare sia == e !=.
Yuck

13
Quindi questo spiega perché dovrebbero avere la possibilità di sovrascrivere entrambi, non perché dovrebbero essere costretti a farlo.
BlueRaja - Danny Pflughoeft il

1
In tal senso, non devono ottimizzare entrambi, devono solo fornire una chiara definizione di entrambi.
Jesper,

22

Se osservi le implementazioni dei sovraccarichi di == e! = Nel sorgente .net, spesso non implementano! = As! (Left == right). Lo implementano completamente (come ==) con logica negata. Ad esempio, DateTime implementa == as

return d1.InternalTicks == d2.InternalTicks;

e! = as

return d1.InternalTicks != d2.InternalTicks;

Se tu (o il compilatore lo facessi implicitamente) dovessi implementare! = As

return !(d1==d2);

allora stai prendendo in considerazione l'implementazione interna di == e! = nelle cose a cui fa riferimento la tua classe. Evitare tale ipotesi potrebbe essere la filosofia alla base della loro decisione.


17

Per rispondere alla tua modifica, riguardo al motivo per cui sei costretto a sovrascrivere entrambi se ne sostituisci uno, è tutto nell'eredità.

Se sostituisci ==, molto probabilmente fornirà una sorta di uguaglianza semantica o strutturale (ad esempio, DateTimes sono uguali se le loro proprietà InternalTicks sono uguali anche se possono essere istanze diverse), allora stai cambiando il comportamento predefinito dell'operatore da Oggetto, che è il genitore di tutti gli oggetti .NET. L'operatore == è, in C #, un metodo, la cui implementazione base Object.operator (==) esegue un confronto referenziale. Object.operator (! =) È un altro metodo diverso, che esegue anche un confronto referenziale.

In quasi tutti gli altri casi di override del metodo, sarebbe illogico presumere che l'override di un metodo comporterebbe anche una modifica comportamentale in un metodo antonimico. Se hai creato una classe con i metodi Increment () e Decrement () e hai annullato Increment () in una classe figlio, ti aspetteresti che anche Decrement () venga sovrascritto con l'opposto del tuo comportamento ignorato? Il compilatore non può essere reso abbastanza intelligente da generare una funzione inversa per l'implementazione di un operatore in tutti i casi possibili.

Tuttavia, gli operatori, sebbene implementati in modo molto simile ai metodi, lavorano concettualmente in coppia; == e! =, <e> e <= e> =. In questo caso sarebbe illogico dal punto di vista di un consumatore pensare che! = Abbia funzionato diversamente da ==. Quindi, il compilatore non può essere fatto supporre che a! = B ==! (A == b) in tutti i casi, ma si prevede generalmente che == e! = Funzionino in modo simile, quindi il compilatore forza di implementare in coppia, tuttavia in realtà finisci per farlo. Se, per la tua classe, a! = B ==! (A == b), implementa semplicemente l'operatore! = Usando! (==), ma se quella regola non vale in tutti i casi per il tuo oggetto (ad esempio , se il confronto con un determinato valore, uguale o ineguale, non è valido), devi essere più intelligente dell'IDE.

La vera domanda che dovrebbe essere posta è perché <e> ​​e <= e> = sono coppie per operatori comparativi che devono essere implementate contemporaneamente, quando in termini numerici! (A <b) == a> = b e! (A> b) == a <= b. Dovresti essere richiesto di implementare tutti e quattro se ne sostituisci uno e probabilmente dovresti anche sovrascrivere == (e! =), Perché (a <= b) == (a == b) se a è semanticamente uguale a b.


14

Se sovraccarichi == per il tuo tipo personalizzato, e non! = Allora sarà gestito dall'operatore! = Per oggetto! = Oggetto poiché tutto deriva dall'oggetto, e questo sarebbe molto diverso da CustomType! = CustomType.

Anche i creatori di lingue probabilmente lo volevano in questo modo per consentire la massima flessibilità ai programmatori e anche per non fare ipotesi su ciò che si intende fare.



2
Il motivo della flessibilità può anche essere applicato a molte funzioni che hanno deciso di lasciare, ad esempio blogs.msdn.com/b/ericlippert/archive/2011/06/23/…. Quindi non spiega la loro scelta per l'implementazione degli operatori di uguaglianza.
Stefan Dragnev,

Questo è molto interessante. Sembra che sarebbe una caratteristica utile. Non ho idea del perché l'avrebbero lasciato fuori.
Chris Mullins,

11

Questo è ciò che mi viene in mente per primo:

  • Che cosa succede se testare la disuguaglianza è molto più veloce del test di uguaglianza?
  • Che cosa succede se in alcuni casi si desidera restituire falsesia per ==che!= (ovvero se non possono essere confrontati per qualche motivo)

5
Nel primo caso è quindi possibile implementare il test di uguaglianza in termini di test di disuguaglianza.
Stefan Dragnev,

Il secondo caso romperà strutture come Dictionarye Listse usi il tuo tipo personalizzato con loro.
Stefan Dragnev,

2
@Stefan: Dictionaryutilizza GetHashCodee Equalsnon gli operatori. Listusi Equals.
Dan Abramov,

6

Bene, probabilmente è solo una scelta di design, ma come dici tu, x!= ynon deve essere la stessa !(x == y). Non aggiungendo un'implementazione predefinita, si è certi di non dimenticare di implementare un'implementazione specifica. E se è davvero banale come dici tu, puoi semplicemente implementarne uno usando l'altro. Non vedo come questa sia una "cattiva pratica".

Potrebbero esserci anche altre differenze tra C # e Lua ...


1
È una pratica eccellente implementare l'uno in termini di altro. L'ho appena visto diversamente, il che è soggetto a errori.
Stefan Dragnev,

3
Questo è il problema del programmatore, non il problema di C #. I programmatori che non implementano questo diritto, falliranno anche in altre aree.
GolezTrol,

@GolezTrol: Potresti spiegare quel riferimento a Lua? Non conosco affatto Lua, ma il tuo riferimento sembra suggerire qualcosa.
zw324,

@Ziyao Wei: OP sottolinea che Lua lo fa diversamente da C # (apparentemente Lua aggiunge delle controparti predefinite per gli operatori citati). Sto solo cercando di banalizzare un simile confronto. Se non ci sono differenze, almeno una di esse non ha il diritto di esistere. Devo dire che non conosco neanche Lua.
GolezTrol,

6

Le parole chiave nella tua domanda sono " why " e " must ".

Di conseguenza:

Rispondere è così perché lo hanno progettato per essere così, è vero ... ma non rispondere alla parte "perché" della tua domanda.

Rispondere che a volte potrebbe essere utile ignorare entrambi questi indipendentemente, è vero ... ma non rispondere alla parte "must" della tua domanda.

Penso che la semplice risposta sia che non esiste alcun motivo convincente per cui C # richiede di ignorare entrambi.

La lingua dovrebbe permettere di ignorare solo ==, e fornire un'implementazione di default di !=che è !quella. Se anche tu vuoi ignorare !=, fallo.

Non è stata una buona decisione. Gli umani progettano i linguaggi, gli umani non sono perfetti, C # non è perfetto. Scrollata di spalle e QED


5

Solo per aggiungere alle risposte eccellenti qui:

Considera cosa accadrebbe nel debugger , quando provi a entrare in un !=operatore e invece finisci in un ==operatore! Parla di confusione!

Ha senso che CLR ti consentirebbe la libertà di tralasciare l'uno o l'altro degli operatori, poiché deve funzionare con molte lingue. Ma ci sono un sacco di esempi di C # non esporre caratteristiche CLR ( refrendimenti e gente del posto, per esempio), e un sacco di esempi di attuazione funzioni non nel CLR stesso (ad esempio: using, lock, foreach, ecc).


3

I linguaggi di programmazione sono riarrangiamenti sintattici di istruzioni logiche eccezionalmente complesse. Con questo in mente, puoi definire un caso di uguaglianza senza definire un caso di non-uguaglianza? La risposta è no. Perché un oggetto a sia uguale all'oggetto b, allora anche l'inverso dell'oggetto a non è uguale a b deve essere vero. Un altro modo per dimostrarlo è

if a == b then !(a != b)

questo fornisce la capacità definita per il linguaggio di determinare l'uguaglianza degli oggetti. Ad esempio, il confronto NULL! = NULL può gettare una chiave inglese nella definizione di un sistema di uguaglianza che non implementa un'istruzione di non uguaglianza.

Ora, per quanto riguarda l'idea di! = Essere semplicemente definizione sostituibile come in

if !(a==b) then a!=b

Non posso discuterne. Tuttavia, molto probabilmente è stata una decisione del gruppo di specifica del linguaggio C # che il programmatore è stato costretto a definire esplicitamente l'uguaglianza e la non uguaglianza di un oggetto insieme


Nullsarebbe solo un problema perché nullè un input valido a ==cui non dovrebbe essere, anche se ovviamente, è enormemente conveniente. Personalmente, penso solo che consenta enormi quantità di programmazione vaga e sciatta.
nicodemus13,

2

In breve, coerenza forzata.

'==' e '! =' sono sempre veri opposti, non importa come li definisci, definiti come tali dalla loro definizione verbale di "uguale" e "non uguale". Definendo solo uno di essi, ti apri a un'incoerenza dell'operatore di uguaglianza in cui sia '==' che '! =' Possono essere entrambi veri o entrambi falsi per due valori dati. Devi definirli entrambi poiché quando scegli di definirne uno, devi anche definire l'altro in modo appropriato in modo che sia palesemente chiaro quale sia la tua definizione di "uguaglianza". L'altra soluzione per il compilatore è consentire solo di sovrascrivere '==' OR '! =' E lasciare l'altro come intrinsecamente negando l'altro. Ovviamente, non è il caso del compilatore C # e sono sicuro che ci '

La domanda che dovresti porre è "perché devo scavalcare gli operatori?" Questa è una decisione forte da prendere che richiede un forte ragionamento. Per gli oggetti, '==' e '! =' Confrontano per riferimento. Se si desidera sovrascriverli per NON confrontare per riferimento, si sta creando un'incoerenza dell'operatore generale che non è evidente a nessun altro sviluppatore che esaminerebbe quel codice. Se si sta tentando di porre la domanda "lo stato di queste due istanze è equivalente?", È necessario implementare IEquatible, definire Equals () e utilizzare la chiamata del metodo.

Infine, IEquatable () non definisce NotEquals () per lo stesso ragionamento: potenziale per aprire incoerenze dell'operatore di uguaglianza. NotEquals () dovrebbe restituire SEMPRE! Equals (). Aprendo la definizione di NotEquals () alla classe che implementa Equals (), si sta ancora una volta forzando il problema della coerenza nel determinare l'uguaglianza.

Modifica: questo è semplicemente il mio ragionamento.


Questo è illogico. Se la ragione fosse la coerenza, si applicherebbe il contrario: si sarebbe solo in grado di implementare operator ==e il compilatore avrebbe inferito operator !=. Dopo tutto, questo è ciò che fa per operator +e operator +=.
Konrad Rudolph,

1
foo + = bar; è un incarico composto, abbreviazione di foo = foo + bar ;. Non è un operatore.
blenderfreaky,

Se infatti essi sono 'sempre vero opposti', allora sarebbe un motivo per definire automaticamente a != bcome !(a == b)... (che non è quello che fa C #).
Andrewf,

-3

Probabilmente solo qualcosa a cui non avevano pensato non aveva tempo di fare.

Uso sempre il tuo metodo quando sovraccarico ==. Quindi lo uso solo nell'altro.

Hai ragione, con una piccola quantità di lavoro, il compilatore potrebbe darcelo gratuitamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.