Condividi tramite


Panoramica dei tipi generici

Gli sviluppatori usano generics sempre in .NET, sia in modo implicito che in modo esplicito. Ti sei mai accorto che stai lavorando con IEnumerable<T> quando usi LINQ in .NET? In alternativa, se è stato visualizzato un esempio online di un "repository generico" per comunicare con i database usando Entity Framework, si è visto che la maggior parte dei metodi restituisce IQueryable<T>? Ci si potrebbe chiedere che cos'è il T in questi esempi e perché è lì dentro.

Introdotti nel .NET Framework 2.0, i generics sono essenzialmente un "modello di codice" che consente agli sviluppatori di definire strutture di dati sicure per i tipi senza impegnarsi con un tipo di dato specifico. Ad esempio, List<T> è una raccolta generica che può essere dichiarata e usata con qualsiasi tipo, ad esempio List<int>, List<string>o List<Person>.

Per comprendere perché i generics sono utili, si esaminerà una classe specifica prima e dopo l'aggiunta di generics: ArrayList. In .NET Framework 1.0 gli ArrayList elementi erano di tipo Object. Qualsiasi elemento aggiunto alla raccolta è stato convertito automaticamente in un oggetto Object. Lo stesso avviene durante la lettura degli elementi dall'elenco. Questo processo, noto come boxing e unboxing, influisce sulle prestazioni. A parte le prestazioni, tuttavia, non c'è modo di determinare il tipo di dati nell'elenco in fase di compilazione, che rende un codice fragile. I generics risolvono questo problema definendo il tipo di dati che ogni istanza dell'elenco conterrà. Ad esempio, è possibile aggiungere solo numeri interi a List<int> e aggiungere solo persone a List<Person>.

I generics sono disponibili anche in fase di esecuzione. Il runtime conosce il tipo di struttura dei dati in uso e può archiviarlo in memoria in modo più efficiente.

L'esempio seguente è un piccolo programma che illustra l'efficienza di conoscere il tipo di struttura dei dati in fase di esecuzione:

  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;

  namespace GenericsExample {
    class Program {
      static void Main(string[] args) {
        //generic list
        List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
        //non-generic list
        ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
        // timer for generic list sort
        Stopwatch s = Stopwatch.StartNew();
        ListGeneric.Sort();
        s.Stop();
        Console.WriteLine($"Generic Sort: {ListGeneric}  \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

        //timer for non-generic list sort
        Stopwatch s2 = Stopwatch.StartNew();
        ListNonGeneric.Sort();
        s2.Stop();
        Console.WriteLine($"Non-Generic Sort: {ListNonGeneric}  \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
        Console.ReadLine();
      }
    }
  }

Questo programma produce output simile al seguente:

Generic Sort: System.Collections.Generic.List`1[System.Int32]
 Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
 Time taken: 0.2592ms

La prima cosa che si può notare qui è che l'ordinamento dell'elenco generico è notevolmente più veloce rispetto all'ordinamento dell'elenco non generico. È anche possibile notare che il tipo per l'elenco generico è distinto ([System.Int32]), mentre il tipo per l'elenco non generico è generalizzato. Poiché il runtime sa che il generico List<int> è di tipo Int32, può archiviare gli elementi dell'elenco in una matrice integer sottostante in memoria, mentre il non generico ArrayList deve eseguire il cast di ogni elemento dell'elenco a un oggetto . Come illustrato in questo esempio, i cast aggiuntivi richiedono tempo e rallentano l'ordinamento dell'elenco.

Un ulteriore vantaggio del runtime che conosce il tipo del tuo generico è una migliore esperienza di debug. Quando si esegue il debug di un generico in C#, si conosce il tipo di ogni elemento nella struttura dei dati. Senza generics, non avresti idea del tipo che ogni elemento era.

Vedere anche