CodeGym /Corsi /C# SELF /Riflessione: System.Reflec...

Riflessione: System.Reflection

C# SELF
Livello 63, Lezione 0
Disponibile

1. Introduzione

La riflessione (dall'inglese Reflection) è la capacità del tuo programma di esplorare la propria struttura durante l'esecuzione. Immagina che il tuo programma possa chiedersi: 'Quali metodi ho? Quali proprietà ha questo tipo?' e ottenere una risposta a runtime.

Esempi di utilizzo della reflection nella vita reale

  • Creazione di plugin e moduli: Non serve conoscere a priori con quale classe lavorerai — basta caricare la DLL e scoprire i suoi metodi al volo.
  • Automazione dei test: I framework di test, per esempio xUnit e NUnit, trovano i metodi di test tramite reflection.
  • Serializzazione: Con la reflection puoi trasformare automaticamente un qualsiasi oggetto in JSON/XML senza enumerare manualmente le proprietà.
  • Validazione dei dati tramite attributi: Per esempio, se hai aggiunto l'attributo [Required] a una proprietà — puoi verificare automaticamente tutte le proprietà così annotate.

Perché è utile saperlo per i colloqui

La capacità di usare la reflection viene spesso richiesta nei colloqui perché dimostra la comprensione dell'architettura .NET e l'abilità di risolvere problemi dinamici. Se vuoi scrivere i tuoi framework o estensioni, la reflection è indispensabile.

2. Spazio dei nomi e classi base della reflection

Tutte le funzionalità della reflection in C# sono disponibili tramite lo spazio dei nomi System.Reflection. All'inizio del file non dimenticare:

using System.Reflection;

Tipi principali della reflection

Classe/Tipo Descrizione
Type
Rappresenta la descrizione di un tipo in .NET (per esempio, classe, interface, enum, ecc.)
PropertyInfo
Proprietà di un tipo
MethodInfo
Metodo di un tipo
FieldInfo
Campo di un tipo
ConstructorInfo
Costruttore di un tipo
Assembly
Rappresenta un assembly (EXE o DLL)

Metodi principali della classe Type

Metodo Descrizione
GetProperties()
Restituisce tutte le proprietà pubbliche
GetFields()
Restituisce tutti i campi pubblici
GetMethods()
Restituisce tutti i metodi pubblici
GetConstructors()
Restituisce tutti i costruttori pubblici
GetInterfaces()
Restituisce tutte le interfacce che il tipo implementa
GetCustomAttributes()
Restituisce tutti gli attributi applicati al tipo

Spesso a questi metodi puoi passare BindingFlags per accedere a membri privati o statici, ma per ora ci limitiamo allo scenario base.

3. Primo passo: ottenere un oggetto Type

Tutte le tecniche della reflection si basano sul lavoro con un oggetto di tipo Type.

Ci sono diversi modi per ottenere questo oggetto, vediamoli:

Metodo 1: Tramite un oggetto

string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String

Metodo 2: Tramite il nome del tipo

Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32

Metodo 3: Da stringa (dinamicamente)

Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double

Attenzione! Per le classi personalizzate in altri assembly è necessario specificare il nome completo del tipo includendo l'assembly.

4. Esplorare la struttura del tipo: proprietà, metodi, campi e costruttori

È arrivato il momento della verità: abbiamo ottenuto l'oggetto Type — e ora possiamo scoprire tutto quello che vogliamo.

Ottenere la lista delle proprietà

Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();

foreach (var prop in properties)
{
    Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}

Output:

Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...

Ottenere la lista dei metodi

MethodInfo[] methods = type.GetMethods();

foreach (var method in methods)
{
    Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}

I tipi grandi possono avere moltissimi metodi! Spesso ti interessano non tutti, ma solo i "tuoi" (per esempio escludendo i metodi ereditati da object). Ne parleremo più avanti.

Ottenere la lista dei campi

FieldInfo[] fields = type.GetFields();

foreach (var field in fields)
{
    Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}

Ma la maggior parte delle classi normali non ha campi pubblici, perché l'incapsulamento è importante :)

Ottenere i costruttori

ConstructorInfo[] constructors = type.GetConstructors();

foreach (var ctor in constructors)
{
    Console.WriteLine($"Costruttore: {ctor}");
}

5. Applichiamo la reflection nella pratica: analizziamo la nostra app

Ricordo che nel nostro progetto didattico esiste la classe TaskItem, dove memorizziamo i task. Vediamola con la reflection.

public class TaskItem
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime DueDate { get; set; }
    public bool IsCompleted;
}

Ecco come ottenere tutti i dettagli del nostro tipo a runtime:

Type taskType = typeof(TaskItem);

Console.WriteLine("Proprietà:");
foreach (var prop in taskType.GetProperties())
    Console.WriteLine($"  {prop.PropertyType.Name} {prop.Name}");

Console.WriteLine("Campi:");
foreach (var field in taskType.GetFields())
    Console.WriteLine($"  {field.FieldType.Name} {field.Name}");

Console.WriteLine("Metodi:");
foreach (var method in taskType.GetMethods())
    Console.WriteLine($"  {method.ReturnType.Name} {method.Name}()");

Prova ad aggiungere nuove proprietà alla classe e guarda come la reflection le rileva. Così funzionano i serializer universali e gli object inspector.

6. Visualizzazione: com'è fatto l'oggetto Type (schema)


+-------------------------+
|         Type            |
+-------------------------+
|  .Name                  |
|  .FullName              |
|  .Namespace             |
|  .Assembly              |
+-------------------------+
|  .GetProperties()       |
|  .GetFields()           |
|  .GetMethods()          |
|  .GetConstructors()     |
|  .GetInterfaces()       |
|  .GetCustomAttributes() |
+-------------------------+

Questo è il "ritratto del tipo" — ha un nome, un assembly, un insieme di membri e attributi.

Ottenere informazioni di base sul tipo

L'oggetto Type può dire che tipo è:

Type t = typeof(double);

Console.WriteLine(t.Name);         // Double
Console.WriteLine(t.FullName);     // System.Double
Console.WriteLine(t.Namespace);    // System
Console.WriteLine(t.IsClass);      // False
Console.WriteLine(t.IsValueType);  // True
Console.WriteLine(t.IsEnum);       // False
Console.WriteLine(t.IsPrimitive);  // True

7. Cercare una proprietà, un metodo o un campo per nome

Proprietà

var prop = taskType.GetProperty("Title");
if (prop != null)
{
    Console.WriteLine($"Tipo della proprietà 'Title': {prop.PropertyType}");
}

Metodo

var method = taskType.GetMethod("ToString");
if (method != null)
{
    Console.WriteLine($"Il metodo 'ToString' restituisce: {method.ReturnType}");
}

Campo

var field = taskType.GetField("IsCompleted");
if (field != null)
{
    Console.WriteLine($"Tipo del campo 'IsCompleted': {field.FieldType}");
}

8. Usare la reflection per accesso dinamico ai valori

Ora non ci limitiamo a guardare, ma possiamo anche toccare i valori!

Leggere il valore di una proprietà

TaskItem item = new TaskItem { Id = 1, Title = "Testare la reflection", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");

if (titleProp != null)
{
    object value = titleProp.GetValue(item);
    Console.WriteLine($"Titolo: {value}");
}

Impostare il valore di una proprietà

titleProp.SetValue(item, "Titolo modificato");
Console.WriteLine(item.Title);

Lavorare con i campi

FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true

Per campi e proprietà privati servono flag speciali, per esempio BindingFlags.NonPublic. Ne parleremo più avanti.

9. Invocare metodi tramite reflection

Puoi invocare metodi anche senza conoscerli a compile-time!

TaskItem task = new TaskItem { Title = "Reflection — bella!" };
MethodInfo method = task.GetType().GetMethod("ToString");

if (method != null)
{
    object result = method.Invoke(task, null);
    Console.WriteLine(result);
}

Se il metodo ha parametri, devi passarli come array di oggetti:

public class Calculator
{
    public int Add(int a, int b) => a + b;
}

var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8

10. Creare oggetti dinamicamente tramite costruttore

La reflection permette di creare oggetti anche senza l'operatore new!

Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// È lo stesso TaskItem (ma il tipo è object)
Console.WriteLine(newTask.GetType().Name); // TaskItem

È possibile passare argomenti al costruttore:

public class User
{
    public string Name { get; }
    public User(string name) { Name = name; }
}

Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "Elis");
Console.WriteLine(((User)user).Name); // Elis

11. Errori tipici quando si lavora con System.Reflection

Errore №1: Tentare di accedere a membri privati senza BindingFlags.
Per default la reflection vede solo i membri pubblici. Senza BindingFlags.NonPublic l'accesso a campi o metodi privati non è possibile.

Errore №2: Ignorare il controllo su null.
I metodi GetProperty, GetMethod e altri possono restituire null se il membro non è trovato. Senza controllo questo porta a NullReferenceException.

Errore №3: Scelta errata della overload del metodo.
Se ci sono più overload di un metodo, GetMethod potrebbe restituire la variante sbagliata. Usa GetMethod specificando i tipi dei parametri.

Errore №4: Usare la reflection in codice ad alte prestazioni.
La reflection è più lenta delle chiamate dirette. Per codice ad alta performance usa caching dei metadati o delegate.

2
Compito
C# SELF, livello 63, lezione 0
Bloccato
Ottenere l'elenco delle proprietà, dei metodi e dei campi di un tipo
Ottenere l'elenco delle proprietà, dei metodi e dei campi di un tipo
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION