CodeGym /Corsi /C# SELF /Uso della reflection

Uso della reflection

C# SELF
Livello 63, Lezione 1
Disponibile

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
Type
Il punto di ingresso principale — tutto parte da qui
FieldInfo
Informazioni su un field
PropertyInfo
Informazioni su una property
MethodInfo
Informazioni su un metodo
ConstructorInfo
Informazioni su un costruttore
EventInfo
Informazioni su un evento
MemberInfo
“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
GetFields(BindingFlags.Public | ...)
Tutto ciò che è visibile agli altri
Campi privati
GetFields(BindingFlags.NonPublic | ...)
Nascosti interni
Solo istanza
BindingFlags.Instance
Solo non statici
Solo static
BindingFlags.Static
Solo statici
Tutto insieme
BindingFlags.Public | NonPublic | Instance | Static
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)
GetField, GetFields
FieldInfo
.GetValue(obj), .SetValue(obj, x)
Proprietà (prop)
GetProperty, GetProperties
PropertyInfo
.GetValue(obj), .SetValue(obj, x)
Metodo (method)
GetMethod, GetMethods
MethodInfo
.Invoke(obj, parameters)
Costruttore
GetConstructor, GetConstructors
ConstructorInfo
.Invoke(parameters)

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.

2
Compito
C# SELF, livello 63, lezione 1
Bloccato
Chiamata di un metodo tramite reflection
Chiamata di un metodo tramite reflection
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION