TIPI PREDEFINITI DI C#

Tutti i linguaggi Object Oriented tendono a dividere i tipi di dato in due famiglie: i cosiddetti Value Types e i cosiddetti Reference Types. Visto che sarebbe impossibile scrivere programmi, anche semplici, senza conoscere le differenze tra questi due tipi di dato, più avanti approfondiremo bene che cosa sono e perché è fondamentale conoscere le loro proprietà, in modo da evitare di ritrovarci a gestire comportamenti imprevedibili da parte dei nostri programmi.

Queste due famiglie racchiudono al loro interno diversi tipi predefiniti (built-in types), i quali sono:

Per i Value Types troviamo i tipi:

  •         Bool;
  •         Byte;
  •         Char;
  •         Decimal;
  •         Double;
  •         Float;
  •         Int;
  •         Long;
  •         Short;

All’interno dei Reference Types troviamo invece:

  •         Oggetti (ovvero istanze delle classi);
  •         Stringhe;
  •         Dynamic;

Nella tabella qui di seguito vengono elencate le caratteristiche dei tipi numerici, evidenziandone range numerico e dimensione:

Il range è determinante nella scelta del tipo numerico da usare quando dichiariamo una variabile. Facciamo un esempio: il byte con il segno (sbyte) ci permette di esprimere dei numeri che vanno da -128 a 127; mentre il byte senza segno (byte), ci permette di esprimere solo numeri positivi, ovvero da 0 a 255. Questo cosa significa? Significa che un bit all’interno del sbyte viene usato per esprimere il concetto del più e del meno, mentre nel caso del byte vengono usati tutti e 8 i bit per esprimere il valore. È essenziale quindi valutare bene che tipo di informazione andremo a memorizzare in quella variabile per evitare di ritrovarci fuori range, non potendo eventualmente rappresentare il numero che vogliamo.

Altra cosa fondamentale per quanto riguarda i tipi numerici è la dimensione che essi occupano. Spesso, quando scriviamo il codice, per semplicità e brevità usiamo int per esprimere un numero. Questa pratica di per sé è giusta, ma prende un altro risvolto nel momento in cui abbiamo la necessità di ottimizzare l’utilizzo di memoria, per cui, esattamente come accade per il range, è importante avere chiaro sin dall’inizio lo scopo della variabile che andiamo a dichiarare. Ammettiamo che il massimo valore che può assumere una variabile sia 100: è molto meglio utilizzare 8 bit ed usare un byte, andando così a dividere per 4 l’occupazione in memoria, che utilizzarne 32 con un int; in questo modo risparmiamo memoria e ottimizziamo le prestazioni, garantendo così una maggiore compatibilità per i sistemi meno performanti.

Nella seguente tabella vengono invece elencati i numeri con la virgola, per i quali valgono le stesse accortezze di range e dimensione spiegate per gli interi:

ENUM

Un altro tipo di dato che comunemente viene utilizzato in diversi linguaggi, tra cui C++ e Java, sono gli enum, o enumerazioni, i quali rappresentano un insieme costante di valori.

Nell’esempio, l’enum che si è definito si chiama Season, e può assumere solo quattro possibili valori, ovvero: Spring, Summer, Autumn, Winter. Di solito, è buona norma andare a scrivere degli enum quando abbiamo un numero predefinito di valori che conosciamo già in partenza, avendo così un modo semplice e sintetico per esprimere una sequenza di valori. Nel mondo del C# ogni valore dell’enum coincide con un numero intero, quindi Season.Spring coincide con il numero zero, Season.Summer coincide con il numero uno e così via. A tal proposito, esistono dei metodi che ci consentono di recuperare il valore numerico dal valore testuale e viceversa, essendo però questi delle tematiche avanzate, si rimanda alla documentazione ufficiale per ulteriori approfondimenti.

STRUCT

Le struct sono un concetto che nasce dal C e dal C++, e sono un tipo di dato in grado di incapsulare dati e funzionalità correlate.

Nell’esempio stiamo definendo una struct Coords che rappresenta delle coordinate cartesiane. Possiamo notare al suo interno: un costruttore, il quale accetta in ingresso due double x e y; due getter X e Y, che ci ritornano il valore delle coordinate (volutamente senza setter, visto che i valori in questione una volta definiti non sono più modificabili); un metodo ToString che fa ritornare una stringa interpolata con i valori di X e Y.

A questo punto ci staremo domandando: quando usiamo una struct, un enum o una classe?

L’enum è il tipo più semplice, sostanzialmente è una sequenza di valori predeterminati in partenza, quindi se sappiamo di avere dei valori già definiti, l’enum è la scelta migliore. Se invece abbiamo la necessità di esprimere un insieme di dati, con pochissimi metodi che li manipolano, la scelta più appropriata è la struct. Nel caso in cui, oltre ai dati, abbiamo anche la necessità di avere svariati metodi che vanno a lavorare su di essi, è preferibile usare una classe.

TUPLE

Un’altra struttura dati decisamente interessante, peculiare del C#, sono le tuple. Le tuple sono di fatto una struttura che ci consente di raggruppare un insieme di più dati nella stessa variabile.

Qui nell’esempio viene innanzitutto dichiarata t1, una tupla che non è solo di tipo double o int ma è di tipo ”coppia” double-int, contenendo di fatto due valori: uno per la parte double ed uno per la parte int. Analizzando meglio la dichiarazione possiamo notare che 4.5 è il numero che valorizza la componente double di t1, mentre 3 è il valore che inizializza la componente int. Per accedere ai due componenti di t1, ci sono due properties implicite delle tuple, ovvero: Item1 ed Item2, le quali permettono appunto di accedere al double scrivendo t1.Item1 o di accedere all’int con t1.Item2. Chiaramente, il numero di items presenti all’interno della tupla possono essere infiniti, non ci sono limiti.

Accedere con Item1, Item2, Item3 etc. può risultare delirante se la tupla che andiamo a definire è molto complessa. Allora come faccio a capire che Item1 si riferisce ad un certo elemento, Item2 ad un altro e così via? Per ovviare a questo si assegna un nome ai componenti della tupla, come si può evincere dal secondo esempio t2. Nella tupla t2, infatti, il double viene chiamato Sum e l’int viene chiamato Count, per cui questo permette di accedere direttamente all’elemento in questione scrivendo t2.Sum o t2.Count, rendendo il tutto meno confusionario.

Nel terzo esempio, t3, il ragionamento è simile a t2 ma inverso, ovvero: la tupla viene dichiarata inizialmente come un var generico e, solamente in seguito, quando viene inizializzata, le viene assegnato un nome per ogni singolo componente.

Ciò che può risultare  estremamente interessante con le tuple è che, quando si scrivono dei metodi, si possono effettivamente restituire più valori, basta sapere come fare. Prendiamo ad esempio questo metodo:

Qui immaginiamo di avere un array xs composto da 4 interi, dei quali vogliamo calcolare minimo e massimo. Per fare ciò, si va innanzitutto a scrivere un metodo che ha come parametro d’ingresso l’array di interi int[] input, specificando come tipo di ritorno una tupla composta da due int, int min e int max. Quando richiameremo il metodo da codice, ciò che accadrà è che verrà restituita una tupla contenente i due valori che ci servono.

Il materiale è tratto dalle lezioni IPID svolte da Marco Pirruccio di Heartwood Labs/Operaludica.