This article is currently in the process of being translated into Spanish (~45% done).
A Reflection based settings class
Ok, entonces pensé en finalizar esta parte del tutorial acerca de Reflection con un ejemplo útil e interesante. Es un poco mas grande que los ejemplos usuales de este sitio, pero tengo la espaeranza de que lo vas a aencontrar verdaderamente útil. Utiliza el material que hemos estado viendo en los últimos capítulos, así que espero puedas mantener el ritmo.
Un escenario común al momento de crear cualquier tipo de aplicación es el de salvar la configuración del usuario. Cuando tienes varias configuraciones probablemente crees una clase Configuración, la cual se ocupará de manejar el guardado y la carga de la configuración deseada. Cada vez que necesites una nueva configuración en tu clase Configuración, tendrás que modificar los métodos Cargar() y Guardar() para incluir esas configuraciones. Pero espera, por que no dejar que la clase Configuración descubra sus propias propiedades y después las cargue y salve automáticamente? Con Reflection es bastante fácil, y si has leído los otros capítulos en la section Reflection de este tutorial, deberías ser capaz de entender el ejemplo siguiente.
Para que encaje mejor en un ejemplo pequeño, estoy guardando información sobre una persona en lugar de configuraciones de una aplicación, pero espero que aun así entiendas la idea general. Ten en cuenta que usar Reflection SERÁ más lento que leer y escribir propiedades conocidas manualmente, así que deberías considerar cuándo usarlo y cuándo optar por un enfoque más rápido. Además, en nuestro ejemplo usamos un archivo de texto simple para almacenar valores aún más simples, solo separados por un carácter | (barra vertical). Si vas a usar esto en un entorno real, probablemente quieras un formato mejor para tus datos, quizás XML. Y, por supuesto, no hay mucho manejo de errores, así que probablemente deberías agregar algo de eso también.
Okay, let's get started. First, our Person class, which you can simply rename to Settings or something like that, to make it more useful to you:
public class Person
{
private int age = -1;
private string name = String.Empty;
public void Load()
{
if(File.Exists("settings.dat"))
{
Type type = this.GetType();
string propertyName, value;
string[] temp;
char[] splitChars = new char[] { '|' };
PropertyInfo propertyInfo;
string[] settings = File.ReadAllLines("settings.dat");
foreach(string s in settings)
{
temp = s.Split(splitChars);
if(temp.Length == 2)
{
propertyName = temp[0];
value = temp[1];
propertyInfo = type.GetProperty(propertyName);
if(propertyInfo != null)
this.SetProperty(propertyInfo, value);
}
}
}
}
public void Save()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
TextWriter tw = new StreamWriter("settings.dat");
foreach(PropertyInfo propertyInfo in properties)
{
tw.WriteLine(propertyInfo.Name + "|" + propertyInfo.GetValue(this, null));
}
tw.Close();
}
public void SetProperty(PropertyInfo propertyInfo, object value)
{
switch(propertyInfo.PropertyType.Name)
{
case "Int32":
propertyInfo.SetValue(this, Convert.ToInt32(value), null);
break;
case "String":
propertyInfo.SetValue(this, value.ToString(), null);
break;
}
}
public int Age
{
get { return age; }
set { age = value; }
}
public string Name
{
get { return name; }
set { name = value; }
}
}
Okay, there's a lot of stuff, I know. But I will help you through the entire class. First of all, we have a couple of private fields, for holding information about our person. In the bottom of the class, we have the corresponding public properties which uses the private fields of course.
We also have a Load() method. It looks for the file settings.dat, and if it exists, it reads the entire file and places each line of it in an array of strings. Now, we run through each setting line, and splits it up into two parts: A property name and a value part. If both is present, we simply use the Type object to get the property with the property name, and then we set the value for it, using our own SetProperty method.
The SetProperty() method looks at the type of the property about to be changed, and then acts correspondingly. Right now, it only supports integers and strings, but as you probably realize, extending this support would be quite easy.
The Save() method gets an array of PropertyInfo instances, one for each of the defined properties on the Person class, and then uses a TextWriter to write each property, and its value, to the data file.
Now we just need some code to use this class. This small application will try to load the person from the settings file, and if it doesn't succeed, the user is prompted for the information:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Load();
if((person.Age > 0) && (person.Name != String.Empty))
{
Console.WriteLine("Hi " + person.Name + " - you are " + person.Age + " years old!");
}
else
{
Console.WriteLine("I don't seem to know much about you. Please enter the following information:");
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties();
foreach(PropertyInfo propertyInfo in properties)
{
Console.WriteLine(propertyInfo.Name + ":");
person.SetProperty(propertyInfo, Console.ReadLine());
}
person.Save();
Console.WriteLine("Thank you! I have saved your information for next time.");
}
Console.ReadKey();
}
}Everything here is pretty trivial, except for the part where we ask the user for information. Once again, we use Reflection, to get all the public properties of the Person class, and then ask for each of them.
As a reader exercise, I suggest that you extend the Person class to include more information. Simply add more properties to it, and you will see that this information gets saved and loaded too.