Ho letto il post di Lanny stamattina e ho deciso che probabilmente
era il caso di postare un esempietto su come sia preferibile lavorare con la
reflection per evitare i problemi più comuni. L'esempio di Lanny funziona ed è
utile per capire come funziona la reflection, ma dovendo lavorare con essa è
opportuno fare affidamento su una serie di classi belle e pronte che il
framework mette a disposizione. Oltre a snellire il lavoro eliminando la
necessità di riscrivere del codice che è già perfettamente funzionane,
consentono soprattutto di avere un modo affidabile di operare conversioni di
tipo senza incorrere nei comuni errori dovuti alla globalization. Ecco uno
spezzone di codice commentato che mostra come creare un tipo, settarne le
proprietà e operare delle conversioni da stringa a data utilizzando le
comodissime TypeDescriptor e TypeConverter:
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Globalization;
namespace TestTipi
{
class Program
{
static void Main(string[] args)
{
// ottengo il riferimento al tipo da istanziare
Type personType =
Type.GetType("TestTipi.Person, TestTipi");
// creo una istanza del tipo
object p =
TypeDescriptor.CreateInstance(null, personType, null, null);
// ottengo una collection delle property dell'istanzaa
PropertyDescriptorCollection personProperties =
TypeDescriptor.GetProperties(p);
// setto FirstName
PropertyDescriptor firstName =
personProperties.Find("FirstName", false);
firstName.SetValue(p, "Andrea");
// setto LastName
PropertyDescriptor lastName =
personProperties.Find("LastName", false);
lastName.SetValue(p, "Boschin");
// setto Birthday
PropertyDescriptor birthDay =
personProperties.Find("Birthday", false);
TypeConverter converterToDate =
TypeDescriptor.GetConverter(birthDay.PropertyType);
birthDay.SetValue(
p,
converterToDate.ConvertFromString(
null,
CultureInfo.CreateSpecificCulture("en-US"),
"10/27/1968"));
// dump sulla console
Console.WriteLine(p);
Console.ReadLine();
}
}
/// <summary>
/// classina di test
/// </summary>
class Person
{
private string firstName;
private string lastName;
private DateTime birthday;
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public DateTime Birthday
{
get { return birthday; }
set { birthday = value; }
}
public override string ToString()
{
return string.Format(
"{0} {1} ({2})",
this.FirstName,
this.LastName,
DateTime.Now.Year - this.Birthday.Year);
}
}
}
Due note rapide sull'esempio: per creare l'istanza di un
tipo è opportuno evitare l'uso di Assemply.LoadFrom() ovunque possibile.
piuttosto è preferibile usare come nell'esempio la Type.GetType() passando il
nome completo del tipo includendo anche version, culture e publickeytoken
qualora l'assembly si trovi in GAC o sia firmato. Usare il riferimento al nome
del file non è una buona politica perchè così facendo si esclude tutto il
meccanismo di ricerca di fusion. La Type.GetType() è in grado di lavorare
perfettamente anche con gli assembly autogenerati a patto che vengano messi in
una directory che si trova nell'ambito del probing di fusion.
Secondo: convertire da stringa a qualsiasi tipo e viceversa
è un'attività insidiosa tanto che nel framework ci sono svariati errori proprio su questo argomento. Meglio perciò usare la
TypeConverter che per inciso è un grado di fare conversioni anche su tipi
particolari come ad esempio le Unit. TypeDescriptor ha un bellissimo metodo
GetConverter() che è in grado di istanziare l'opportuno convertitore
semplicemente passandogli il tipo di destinazione. Okkio quindi che la Culture
va passata SEMPRE!
Infine, attenzione ai null. Io nel post non ho messo alcun
controllo per non risultare chilometrico, ma e' evidente che ogni volta che
chiediamo di istanziare un tipo, un assembly, un convertitore, etc... dobbiamo
verificare opportunamente che tutto sia andato a buon
fine.