Operatori di Assegnazione in C#

Edoardo Midali
Edoardo Midali

Gli operatori di assegnazione in C# sono strumenti fondamentali che consentono di assegnare valori alle variabili e, nel caso degli operatori composti, di eseguire operazioni aritmetiche o logiche insieme all’assegnazione. Questi operatori rendono il codice più conciso e leggibile, fornendo un modo elegante per modificare i valori delle variabili attraverso sintassi compatta ed espressiva.

Operatore di Assegnazione Base

Operatore = (Assegnazione Semplice)

L’operatore = è l’operatore di assegnazione fondamentale che assegna il valore dell’espressione di destra alla variabile di sinistra.

int numero = 42;
string nome = "Mario";
bool attivo = true;
double prezzo = 19.99;

// Assegnazione multipla (stesso valore)
int a, b, c;
a = b = c = 10; // Tutte le variabili ricevono il valore 10

Tabella Completa degli Operatori di Assegnazione

Operatore Nome Esempio Equivalente Descrizione
= Assegnazione x = 5 - Assegna valore alla variabile
+= Addizione e assegnazione x += 3 x = x + 3 Somma e assegna
-= Sottrazione e assegnazione x -= 3 x = x - 3 Sottrae e assegna
*= Moltiplicazione e assegnazione x *= 3 x = x * 3 Moltiplica e assegna
/= Divisione e assegnazione x /= 3 x = x / 3 Divide e assegna
%= Modulo e assegnazione x %= 3 x = x % 3 Calcola resto e assegna
&= AND bitwise e assegnazione x &= 3 x = x & 3 AND bitwise e assegna
|= OR bitwise e assegnazione x |= 3 x = x | 3 OR bitwise e assegna
^= XOR bitwise e assegnazione x ^= 3 x = x ^ 3 XOR bitwise e assegna
<<= Shift sinistro e assegnazione x <<= 2 x = x << 2 Shift a sinistra e assegna
>>= Shift destro e assegnazione x >>= 2 x = x >> 2 Shift a destra e assegna
??= Null-coalescing e assegnazione x ??= 5 x = x ?? 5 Assegna se null (C# 8.0+)

Operatori Aritmetici Composti

Addizione e Assegnazione (+=)

int contatore = 10;
contatore += 5; // contatore diventa 15

// Con stringhe (concatenazione)
string messaggio = "Ciao";
messaggio += " Mondo"; // messaggio diventa "Ciao Mondo"

// Con collezioni
List<int> numeri = new List<int> {1, 2, 3};
// numeri += 4; // Errore! Non supportato direttamente
// Usa numeri.Add(4) invece

Sottrazione e Assegnazione (-=)

double saldo = 1000.50;
saldo -= 250.00; // saldo diventa 750.50

int vite = 3;
vite -= 1; // vite diventa 2

Moltiplicazione e Assegnazione (*=)

int area = 5;
area *= 4; // area diventa 20

double prezzo = 19.99;
prezzo *= 1.22; // Applica IVA del 22%

Divisione e Assegnazione (/=)

double totale = 100.0;
totale /= 4; // totale diventa 25.0

int punteggio = 85;
punteggio /= 5; // punteggio diventa 17 (divisione intera)

Modulo e Assegnazione (%=)

int numero = 17;
numero %= 5; // numero diventa 2 (resto di 17/5)

// Utile per operazioni cicliche
int indice = 10;
int dimensioneArray = 7;
indice %= dimensioneArray; // indice diventa 3

Operatori Bitwise Composti

AND Bitwise e Assegnazione (&=)

int flags = 0b1111; // 15 in binario
flags &= 0b1010;    // flags diventa 0b1010 (10)

// Esempio con enum flags
[Flags]
enum Permessi
{
    Nessuno = 0,
    Lettura = 1,
    Scrittura = 2,
    Esecuzione = 4
}

Permessi userPermessi = Permessi.Lettura | Permessi.Scrittura | Permessi.Esecuzione;
userPermessi &= ~Permessi.Esecuzione; // Rimuove permesso di esecuzione

OR Bitwise e Assegnazione (|=)

int flags = 0b1010; // 10 in binario
flags |= 0b0101;    // flags diventa 0b1111 (15)

// Aggiunta di permessi
Permessi userPermessi = Permessi.Lettura;
userPermessi |= Permessi.Scrittura; // Aggiunge permesso di scrittura

XOR Bitwise e Assegnazione (^=)

int numero = 0b1010; // 10
numero ^= 0b1100;    // numero diventa 0b0110 (6)

// Toggle di bit specifici
bool stato = true;
stato ^= true; // stato diventa false (toggle)

Operatori di Shift Composti

Shift Sinistro e Assegnazione (<<=)

int valore = 5;        // 0b0101
valore <<= 2;          // valore diventa 20 (0b10100)

// Equivale a moltiplicare per 2^n
int numero = 3;
numero <<= 3; // numero diventa 24 (3 * 2^3)

Shift Destro e Assegnazione (>>=)

int valore = 20;       // 0b10100
valore >>= 2;          // valore diventa 5 (0b0101)

// Equivale a dividere per 2^n
int numero = 32;
numero >>= 3; // numero diventa 4 (32 / 2^3)

Operatore Null-Coalescing e Assegnazione (??=)

Introdotto in C# 8.0, questo operatore assegna un valore solo se la variabile è null.

string nome = null;
nome ??= "Valore predefinito"; // nome diventa "Valore predefinito"

string cognome = "Esistente";
cognome ??= "Nuovo valore";    // cognome rimane "Esistente"

// Utile per inizializzazione lazy
private List<string> _elementi;
public List<string> Elementi
{
    get
    {
        _elementi ??= new List<string>();
        return _elementi;
    }
}

// Con proprietà nullable
public class Utente
{
    public string? Email { get; set; }

    public void ImpostaEmailPredefinita()
    {
        Email ??= "noreply@example.com";
    }
}

Esempi Pratici

Contatori e Accumulatori

public class ContatorePunteggio
{
    private int punteggio = 0;
    private int moltiplicatore = 1;

    public void AggiungiPunti(int punti)
    {
        punteggio += punti * moltiplicatore;
    }

    public void ApplicaBonus()
    {
        moltiplicatore *= 2;
    }

    public void ApplicaPenalita()
    {
        punteggio /= 2;
    }

    public void ResetMoltiplicatore()
    {
        moltiplicatore = 1;
    }
}

Gestione di Configurazioni

public class ConfigurazioneApp
{
    public string? DatabaseUrl { get; set; }
    public int? Timeout { get; set; }
    public bool? DebugMode { get; set; }

    public void ApplicaConfigurazioniPredefinite()
    {
        DatabaseUrl ??= "localhost:5432";
        Timeout ??= 30;
        DebugMode ??= false;
    }

    public void AggiornaTimeout(int incremento)
    {
        Timeout = Timeout ?? 30;
        Timeout += incremento;
    }
}

var config = new ConfigurazioneApp();
config.ApplicaConfigurazioniPredefinite();
config.AggiornaTimeout(10); // Timeout diventa 40

Manipolazione di Collezioni

public class GestoreStatistiche
{
    private Dictionary<string, int> statistiche = new();

    public void IncrementaStatistica(string chiave)
    {
        if (statistiche.ContainsKey(chiave))
            statistiche[chiave] += 1;
        else
            statistiche[chiave] = 1;
    }

    public void AggiungiPunti(string chiave, int punti)
    {
        statistiche.TryGetValue(chiave, out int valoreAttuale);
        statistiche[chiave] = valoreAttuale + punti;
    }

    public void ApplicaMoltiplicatore(string chiave, double moltiplicatore)
    {
        if (statistiche.ContainsKey(chiave))
        {
            statistiche[chiave] = (int)(statistiche[chiave] * moltiplicatore);
        }
    }
}

Operatori di Assegnazione con Tipi Personalizzati

Overload degli Operatori

public struct Vettore
{
    public double X { get; set; }
    public double Y { get; set; }

    public Vettore(double x, double y)
    {
        X = x;
        Y = y;
    }

    public static Vettore operator +(Vettore a, Vettore b)
    {
        return new Vettore(a.X + b.X, a.Y + b.Y);
    }

    public static Vettore operator *(Vettore v, double scalare)
    {
        return new Vettore(v.X * scalare, v.Y * scalare);
    }
}

// Utilizzo con operatori composti
Vettore velocità = new Vettore(10, 5);
Vettore accelerazione = new Vettore(2, 1);

velocità += accelerazione;      // Usa l'overload di +
velocità *= 1.5;               // Usa l'overload di *

Proprietà con Operatori Composti

public class BankAccount
{
    private decimal _saldo;

    public decimal Saldo
    {
        get => _saldo;
        private set => _saldo = value;
    }

    public void Deposita(decimal importo)
    {
        Saldo += importo;
    }

    public bool Preleva(decimal importo)
    {
        if (Saldo >= importo)
        {
            Saldo -= importo;
            return true;
        }
        return false;
    }

    public void ApplicaInteressi(decimal tasso)
    {
        Saldo *= (1 + tasso);
    }
}

Best Practices e Considerazioni

Performance e Leggibilità

// ✅ Operatori composti per chiarezza
contatore += 1;
saldo -= commissione;
prezzo *= fattoreSconto;

// ❌ Equivalente ma meno leggibile
contatore = contatore + 1;
saldo = saldo - commissione;
prezzo = prezzo * fattoreSconto;

// ✅ Per incrementi semplici, considera ++ e --
contatore++; // Invece di contatore += 1
indice--;    // Invece di indice -= 1

Attenzione ai Tipi

// Attenzione alla conversione di tipi
int intero = 10;
double decimale = 3.7;

// intero += decimale; // Errore di compilazione
intero += (int)decimale; // Cast esplicito necessario

// Con tipi nullable
int? nullable = null;
// nullable += 5; // Errore! nullable è null
nullable = (nullable ?? 0) + 5; // Gestione sicura

Operatori Bitwise - Casi d’Uso

// Gestione di flag
[Flags]
public enum OpzioniFile
{
    Nessuna = 0,
    Lettura = 1,
    Scrittura = 2,
    Nascosto = 4,
    Sistema = 8
}

OpzioniFile opzioni = OpzioniFile.Lettura;
opzioni |= OpzioniFile.Scrittura;    // Aggiunge scrittura
opzioni &= ~OpzioniFile.Nascosto;    // Rimuove nascosto (se presente)

// Operazioni su bit per ottimizzazione
public class BitSet
{
    private uint bits = 0;

    public void Set(int posizione)
    {
        bits |= (uint)(1 << posizione);
    }

    public void Clear(int posizione)
    {
        bits &= ~(uint)(1 << posizione);
    }

    public void Toggle(int posizione)
    {
        bits ^= (uint)(1 << posizione);
    }
}

Errori Comuni da Evitare

Confusione tra = e ==

int x = 5;

// ❌ Errore comune - assegnazione invece di confronto
if (x = 10) // Errore di compilazione in C#
{
    // Questo codice non compilerà
}

// ✅ Confronto corretto
if (x == 10)
{
    // Confronto di uguaglianza
}

Overflow e Underflow

byte valore = 255;
valore += 1; // Overflow! valore diventa 0

// ✅ Controllo sicuro
checked
{
    try
    {
        byte sicuro = 255;
        sicuro += 1; // Genererà OverflowException
    }
    catch (OverflowException)
    {
        Console.WriteLine("Overflow rilevato!");
    }
}

Conclusione

Gli operatori di assegnazione in C# sono strumenti essenziali che rendono il codice più conciso e leggibile. La tabella completa degli operatori mostra la ricchezza di opzioni disponibili, dai semplici operatori aritmetici a quelli bitwise avanzati, fino al moderno operatore null-coalescing. Utilizzare correttamente questi operatori migliora significativamente la qualità del codice, rendendolo più espressivo e manutenibile. È importante comprendere le implicazioni di ogni operatore, especialmente in termini di tipi, performance e comportamento con valori null, per sfruttare al meglio la potenza e la flessibilità di C#.