Fill all fields of an Object in an iterative way

3

I have a Class

public class Objeto {
    public int _numero { get; set; }
    public DateTime _data { get; set; }
    public string _palavra { get; set; }
    public decimal _decimal { get; set; }
}

Is it possible to do what I want with the code below?

That is, feed all the fields of the class with the values of a string Array by doing the conversion to the type of the field.

private void teste() {
    int i = 0;
    Objeto obj = new Objeto();
    string[] array = { "1", "01/01/2015", "abc", "0.123" };
    foreach(PropertyInfo inf in obj.GetType().GetProperties()) {
        System.Diagnostics.Debug.WriteLine(inf.Name);
        System.Diagnostics.Debug.WriteLine(inf.PropertyType);                
        obj_.[inf.Name] = (inf.PropertyType)array[i];
        i++;
    }
}
    
asked by anonymous 05.03.2015 / 16:15

2 answers

1

You need to implement something in your object that determines the order of the properties in order to establish a convention for the order of the parameters in the array.

One option is to create an Attribute to set the order of each property.

There are several advantages to using attributes: you are not subject to the fragility of the field order, you may have more properties in your class even if you do not want to value them from the array, etc.

In addition to the advantages, using attributes, you can define more metadata such as the format of the date received in the string, number of decimal places, required properties, validations, etc. And you can also create a graphical interface to show the updated layout of the expected layout (order and type of fields, data format, etc.) from these attribute metadata.

In this solution, I take advantage of the attributes to specialize the string conversion:

public class DadoAttribute : Attribute
{
    public int Ordem { get; private set; }
    public DadoAttribute(int ordem)
    {
        this.Ordem = ordem;
    }
    public virtual object ConverteValor(string valor, Type type)
    {
        return  Convert.ChangeType(valor, type);
    }
}

See that the attribute knows the conversion logic, so I can implement specific attributes for specific types, such as date:

public class DadoData : DadoAttribute
{
    private String formatoData;
    public DadoData(int ordem, String formatoData) : base(ordem) 
    {
        this.formatoData = formatoData;
    }
    public override object ConverteValor(string valor, Type type)
    {
        return DateTime.ParseExact(valor, formatoData, null);
    }
}

And then you can decorate each property by stating your order and eventually an attribute with special data conversion capability, like this:

public class Objeto
{
    [Dado(0)]
    public int _numero { get; set; }
    [DadoData(1, "dd-MM-yyyy")]
    public DateTime _data { get; set; }
    [Dado(2)]
    public string _palavra { get; set; }
    [Dado(3)]
    public decimal _decimal { get; set; }
}

Note that for the date property I have given you a specialized attribute.

Finally, you get the properties of your object by sorting them by the attribute you entered in each one, and arrow their respective value using the attribute conversion knowledge.

You can create a class to represent the property in order to specialize its logic of setting value in the object using the conversion knowledge contained in the attribute:

public delegate object ConverteValor(string valor, Type type);

public class Propriedade
{
    private ConverteValor conversor;
    private PropertyInfo property;
    public Propriedade(PropertyInfo property, ConverteValor conversor)
    {
        this.conversor = conversor;
        this.property = property;
    }
    public void SetValue(object objeto, string valor)
    {
        property.SetValue(objeto, conversor(valor, property.PropertyType));
    }
}

And the logic to read get the properties and set the value on each looks like this:

public static class FabricaObjeto
{
    public static Objeto Constroi(string[] dadosOrdenados)
    {
        var typeAtributoDado = typeof(DadoAttribute);

        var propriedades =
             from propriedade in typeof(Objeto).GetProperties()
             where Attribute.IsDefined(propriedade, typeAtributoDado)
             orderby
               ((DadoAttribute)Attribute.GetCustomAttribute(propriedade, typeAtributoDado)).Ordem
             select new Propriedade (propriedade,
                ((DadoAttribute)Attribute.GetCustomAttribute(propriedade, typeAtributoDado))
                  .ConverteValor);

        var objeto = new Objeto();
        var indiceDado = 0;

        foreach (var propriedade in propriedades)
        {
            propriedade.SetValue(objeto, dadosOrdenados[indiceDado]);
            indiceDado++;
        }
        return objeto;
    }
}

An example consumer code:

public class Program
{
    public static void Main(string[] args)
    {
        var dadosOrdenados = new string[] { "1", "13-01-2015", "abc", "0.123" };
        var objeto = FabricaObjeto.Constroi(dadosOrdenados);

        Console.WriteLine(
            string.Format("{0} - {1} - {2} - {3}"
            , objeto._numero, objeto._data, objeto._palavra, objeto._decimal));
        // saída: 1 - 13/01/2015 00:00:00 - abc - 0.123
    }
}

This solution is useful, for example, to instantiate objects from delimited text file reading ( csv ). Sometimes we can not dictate the format of the file and our code will never escape needing to know the sequence of fields.

See working at .Net Fiddle .

    
05.03.2015 / 17:19
4

Doing this type of operation always takes a bit of risk, if the order of the data or if the types are not as expected may give problem. The ideal is to try to find another solution, maybe even code generation. But if you need to do this way you have some solutions. I'll give one that works fine if you can ensure the order of the data in the array and the types used are the ones that can be converted with Convert.ChangeType . And of course the data needs to be valid for the conversion to take effect.

using System;
using System.Console;
using System.Reflection;

public class Program {
    public static void Main() {
        int i = 0;
        Objeto obj = new Objeto();
        string[] array = { "1", "01/01/2015", "abc", "0.123" };
        foreach(PropertyInfo inf in typeof(Objeto).GetProperties()) {
            inf.SetValue(obj, Convert.ChangeType(array[i], inf.PropertyType));
            i++;
        }
        WriteLine("Número: {0}", obj._numero);
        WriteLine("Número: {0}", obj._data);
        WriteLine("Número: {0}", obj._palavra);
        WriteLine("Número: {0}", obj._decimal);
    }
}

public class Objeto {
    public int _numero { get; set; }
    public DateTime _data { get; set; }
    public string _palavra { get; set; }
    public decimal _decimal { get; set; }
}

See working on ideone .

You can give more guarantees but it will complicate the code. It is possible to have conversions for other types but would need to set up a table or switch or a class with derivations, or some mechanism that defines all possible types that can be in your objects. And obviously for each type must count a conversion algorithm. Maybe just a delegation to what the type itself already does to convert from strings . It would be somewhat similar to what the function used already does (but could have other implementations as I've said) as seen in your

05.03.2015 / 17:33