Fill all fields of an Object iteratively

Asked

Viewed 2,092 times

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 accomplish what I want with the code below?

That is, feed all class fields with the values of a string array by converting to the field type.

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++;
    }
}
  • 3

    The big problem of using array is to ensure the order of the fields. Nothing guarantees that the application has how to know which field goes where. For this case, the ideal would be a dictionary. At least defined key and value would be simpler to perform the conversion.

  • 1

    If the data in the array comes from reading a delimited text file, the application does not escape having to know the order of the values.

2 answers

4

Doing this type of operation always has a bit of risk, whether the order of the data or if the types are not the expected can give problem. The ideal is to try to find another solution, maybe even generation of code. But if you need to do it this way you have some solutions. I’ll give you one that works well if you can guarantee the order of the data on array and that the types used are those which can be converted with Convert.ChangeType. And of course the data needs to be valid for the conversion to be effective.

using System;
using static 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: {obj._numero}");
        WriteLine($"Número: {obj._data}");
        WriteLine($"Número: {obj._palavra}");
        WriteLine($"Número: {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; }
}

Behold working in the ideone. And in the .NET Fiddle. Also put on the Github for future reference.

It is possible to give more guarantees but complicates the code. It is possible to have conversions for other types but would need to assemble a table or switch or a class with derivations, or some mechanism that defines all possible types that may be in its objects. And obviously for each type you should count a conversion algorithm. Maybe just a delegation to what the guy himself already does to convert from strings. It would be something similar to what the function used already does (but could have other implementations as I said) as can be seen in your source code (no . NET Core).

2


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

One option is to create a Attribute to define the order of each property.

There are several advantages to using attributes: you are not subject to field order fragility, you may have more properties in your class even if you do not want to set value for 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 decimals, required properties, validations, etc. And you can also create a graphical interface to, from these metadata of the attributes, show a documentation always updated of how is the expected layout (order and type of fields, data format, etc.).

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);
    }
}

Note 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 informing its order and eventually an attribute with special data conversion capability, thus:

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 reported a specialized attribute.

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

You can create a class to represent the property in order to specialize its value-setting logic 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 in each one is 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 of 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 create object instances from reading delimited text file (csv). Sometimes we can’t dictate the file format and our code will never escape from knowing the sequence of the fields.

See working on .Net Fiddle.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.