1. Introduzione
In C# ogni classe (o struct) è un insieme di membri. Tra questi ci sono:
- Campi (fields): variabili che conservano dati.
- Proprietà (properties): wrapper “intelligenti” con get/set.
- Metodi (methods): funzionalità.
- Costruttori (constructors): modi per creare un'istanza.
- Eventi (events): publish-subscribe in modo intelligente.
Immagina di avere una lente magica con cui puoi guardare dentro qualsiasi tipo, scoprire cosa contiene e perfino modificarlo. Questa è la reflection.
Namespace e tipi principali della reflection
Prima di iniziare, non dimenticare di includere il namespace:
using System.Reflection;
Classi e interfacce chiave:
| Classe/interfaccia | Descrizione |
|---|---|
|
Il punto di ingresso principale — tutto parte da qui |
|
Informazioni su un field |
|
Informazioni su una property |
|
Informazioni su un metodo |
|
Informazioni su un costruttore |
|
Informazioni su un evento |
|
“Genitore” comune per tutti i precedenti |
2. Otteniamo i membri di un tipo tramite reflection
Otteniamo campi e proprietà
Esempio: enumerare campi e proprietà
Supponiamo di avere questa classe:
public class Person
{
public string Name { get; set; }
public int Age;
private string Secret = "Sekretnoe slovo";
}
Otteniamo i campi:
Type personType = typeof(Person);
FieldInfo[] fields = personType.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var field in fields)
{
Console.WriteLine($"Campo: {field.Name}, Tipo: {field.FieldType.Name}, Accesso: {field.Attributes}");
}
Otteniamo le proprietà:
PropertyInfo[] properties = personType.GetProperties(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Console.WriteLine($"Proprietà: {property.Name}, Tipo: {property.PropertyType.Name}");
}
Spiegazioni
- BindingFlags — è un insieme di “flag” che dicono quali membri cercare (public/private, instance/static ecc.).
- I campi e le proprietà sono diversi: un field è un pezzo di dati, una property sono i metodi get/set che nascondono il field.
Otteniamo i metodi
MethodInfo[] methods = personType.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var method in methods)
{
Console.WriteLine($"Metodo: {method.Name}");
}
Ti sorprenderà quanti metodi verranno stampati. Ci saranno anche i getter/setter delle proprietà e i metodi di servizio di object!
Filtro utile: per ottenere solo i tuoi metodi “espliciti”, puoi filtrare per nome o per attributo.
Cosa cercare con quali BindingFlags
| Cosa cerchiamo | Esempio di chiamata | Descrizione |
|---|---|---|
| Campi pubblici | |
Tutto ciò che è visibile agli altri |
| Campi privati | |
Nascosti interni |
| Solo istanza | |
Solo non statici |
| Solo static | |
Solo statici |
| Tutto insieme | |
Lista completa |
3. Creazione dinamica di un'istanza di un tipo
Perché è figo?
Puoi creare un oggetto senza conoscere il suo tipo esatto a compile time.
Modo semplice — tramite Activator.CreateInstance
// Otteniamo il Type in qualche modo, per esempio:
Type myType = typeof(Person);
// Creiamo l'oggetto:
object obj = Activator.CreateInstance(myType);
// Ora obj è un'istanza di Person, ma come object
Se la classe non ha un costruttore senza parametri, puoi passare i parametri:
public class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
}
// ...
Type animalType = typeof(Animal);
object dog = Activator.CreateInstance(animalType, "Sharik");
Importante!
Console.WriteLine(dog.GetType().Name); // Animal
Metodo più custom — tramite ConstructorInfo
ConstructorInfo ctor = animalType.GetConstructor(new Type[] { typeof(string) });
object dog = ctor.Invoke(new object[] { "Sharik" });
Nella pratica si usa di solito Activator, ma a volte servono parametri precisi o lavorare con costruttori privati — qui ConstructorInfo aiuta.
4. Accesso a campi e proprietà per nome
Ottenere e modificare un campo
Person p = new Person();
Type t = p.GetType();
FieldInfo field = t.GetField("Age");
field.SetValue(p, 42);
Console.WriteLine(field.GetValue(p)); // 42
Lavorare con campi privati:
FieldInfo secret = t.GetField("Secret", BindingFlags.NonPublic | BindingFlags.Instance);
if (secret != null)
{
secret.SetValue(p, "Oj, sekret raskryt");
Console.WriteLine(secret.GetValue(p));
}
else
{
Console.WriteLine("Campo Secret non trovato");
}
Attenzione! Non abusare dell'accesso ai campi privati — è un break dell'incapsulamento e può causare bug potenziali.
Ottenere e modificare le proprietà
PropertyInfo pi = t.GetProperty("Name");
pi.SetValue(p, "Vasya");
Console.WriteLine(pi.GetValue(p)); // Vasya
Le proprietà non sono campi, sono metodi get/set dietro le quinte.
Perché a volte non funziona?
Se il tuo campo o la tua proprietà è privata, assicurati di aggiungere i BindingFlags corretti (per esempio NonPublic). Se la property non ha il setter, non potrai assegnarle nulla tramite reflection — la regola viene rispettata.
5. Invocare metodi per nome
Chiamare un metodo semplice senza parametri
public class Greeter
{
public void SayHello()
{
Console.WriteLine("Privet!");
}
}
Greeter g = new Greeter();
Type t = g.GetType();
MethodInfo mi = t.GetMethod("SayHello");
mi.Invoke(g, null); // Privet!
Chiamare un metodo con parametri
public class MathOp
{
public int Add(int x, int y) => x + y;
}
MathOp calc = new MathOp();
Type t = calc.GetType();
MethodInfo mi = t.GetMethod("Add");
object result = mi.Invoke(calc, new object[] { 7, 5 });
Console.WriteLine(result); // 12
Se il metodo è static, passa null come primo argomento a Invoke.
Lavorare con metodi overloadati
MethodInfo mi = t.GetMethod(
"Add",
new Type[] { typeof(int), typeof(int) }
);
Oppure itera e filtra manualmente:
foreach(var method in t.GetMethods()) {
if (method.Name == "Add" && method.GetParameters().Length == 2)
{
// ... è il nostro metodo!
}
}
6. Note utili
Membri principali, metodi e proprietà per analizzare un tipo
| Membro della classe | Metodo per ottenere info | Classe risultato | Come leggere/modificare il valore |
|---|---|---|---|
| Campo (field) | |
|
|
| Proprietà (prop) | |
|
|
| Metodo (method) | |
|
|
| Costruttore | |
|
|
Perché serve tutto questo nella vita reale?
- Nei framework ORM (per esempio, Entity Framework costruisce query SQL basate sui tuoi modelli).
- Nei sistemi di serializzazione e parser (per esempio, i serializer JSON).
- In librerie generiche per logging, testing, generatori di UI.
- Ai colloqui — piace chiedere “come scriveresti un copier universale di oggetti?”.
- Per scrivere plugin, quando il tuo programma carica e chiama codice scritto da altri.
Anche se ora ti sembra una specie di magia da film di hacker, credimi — ti tornerà utile!
7. Errori tipici e particolarità
Molto spesso gli sviluppatori si confondono con BindingFlags — non vedono i campi privati o, al contrario, ottengono troppi metodi. Controlla sempre la combinazione dei flag: per i campi privati usa NonPublic, per gli statici usa Static ecc.
Quando chiami metodi tramite Invoke passa gli argomenti nello stesso ordine e tipo della dichiarazione. Ordine/tipo sbagliati porteranno a TargetParameterCountException o ArgumentException. Il risultato restituito arriva come object — ricordati di castare il tipo (o usare pattern matching).
Non dimenticare la performance: la reflection è più lenta delle chiamate dirette. Nei punti caldi fai cache dei Type/MethodInfo/PropertyInfo trovati o genera delegati in anticipo.
Spesso vedrai metodi di servizio tipo get_PropertyName e set_PropertyName. Sono i metodi di accesso alle proprietà — non è necessario crearli manualmente.
Se modifichi campi o proprietà private — stai rompendo l'incapsulamento. Un uso sbagliato può causare bug difficili da trovare. Come diceva zio Ben: “Con grande potere viene grande responsabilità” — usalo con cautela, soprattutto in progetti di libreria.
GO TO FULL VERSION