A.Esdeki
A.Esdeki

Reputation: 63

Iterate over a list that has dynamically called by reflection

I have a class that contains a couple of lists of different class types which all of them has a Percent property; I want to get one of these lists dynamically through it's name (i.e., I want to pass the name of the list to the GetPropery method to get the list) and then iterate over the list's entries. The problem is when I use the GetPropery method this will get an object and does not recognize that it is a List; so I cannot loop over it. the class that I want to use it's properties:

public class ModelOfExcel_VM : Object
{
    public ModelOfExcel_VM()
    {
    }

    public List<Sheet_1_VM> Sheet1 { get; set; }
    public List<Sheet_2_VM> Sheet2 { get; set; }
    public List<Sheet_3_VM> Sheet3 { get; set; }
    public List<Sheet_4_VM> Sheet4 { get; set; }
    public List<Sheet_5_VM> Sheet5 { get; set; }
    public List<Sheet_6_VM> Sheet6 { get; set; }
    public List<Sheet_7_VM> Sheet7 { get; set; }
    public List<Sheet_8_VM> Sheet8 { get; set; }
    public List<Sheet_9_VM> Sheet9 { get; set; }
}

Here is the sample code:

private void PercentValidation()
{
    ModelOfExcel_VM excel = new ModelOfExcel_VM;
    var pageswithpercent = new List<string>() { "3", "5", "7", "8", "9"}; 

    foreach (var page in pageswithpercent)
    {
        var thisPage = excel.GetType().GetProperty("Sheet" + page).GetValue(excel, null);

        //this loop will lead to an error due thisPage has type of object and is not iteratable.
        foreach (var item in thisPage)
        {
            Console.WriteLine(item.Percent);
        }

    }
}

I also have used interfaces but it does not work either because when I use the Type of interface instead of the class Type in the foreach loop I can not use the Percent property because it refers to Percent property in IPercent interface rather than Percent property in the original class of the object(each list has a different class type but also has implemented the IPercent interface). If I use varit will refer to object type and then as you know I cannot access properties in the class.

the interface approach:

interface IPercent
{
    float Percent{ get; set; }
}

private void PercentValidation()
{
    ModelOfExcel_VM excel =  new ModelOfExcel_VM;
    var pageswithpercent = new List<string>() { "3", "5", "7", "8", "9"}; 

    foreach (var page in pageswithpercent)
    {
        var thisPageObject = excel.GetType().GetProperty("Sheet" + page).GetValue(excel, null);

        var thisPage = (IEnumerable<IPercent>)thisPageObject;

        //bellow if I use var instead of IPercent as the type of item it would be recognized as object.
        foreach (IPercent item in thisPage)
        {
            //here the percent property refers to interface itself
            Console.WriteLine(item.Percent);
        }
    }
}

it is straightforward in java script but How can I do this in c#?

Upvotes: 1

Views: 346

Answers (1)

canton7
canton7

Reputation: 42350

There are some things about your question which don't make sense:

  1. Your properties are called Sheet1, Sheet2, etc, but you're trying to access properties called Sheet_1_VM, Sheet_2_VM, etc, using reflection
  2. You say you can cast thisPageObject to List<IPercent>, but this is not possible
  3. You say "here the percent property refers to interface itself", but this is not possible. item.Percent has type float, and cannot refer to an interface

However, we can gloss over these issues, and skip to the core question: given an object which might be a List<Sheet_1_VM> or a List<Sheet_2_VM>, where both Sheet_1_VM and Sheet_2_VM implement IPercent, how can we iterate over each element in the list.

The reason you can't cast a List<Sheet_1_VM> to a List<IPercent> is because List<T> is not covariant. A List<Sheet_1_VM> is a list which contains only Sheet_1_VM objects (or their subclasses): you wouldn't be able to put a Sheet_2_VM into a List<Sheet_1_VM>. However, if you pretended that your List<Sheet_1_VM> was a List<IPercent>, then you would be able to put any object which implemented IPercent into it (by calling List<T>.Add). That means you would be able to put a Sheet_2_VM into your List<Sheet_1_VM>, which obviously makes no sense!

However, List<T> implements IEnumerable<T>, and IEnumerable<T> is covariant. This means that it only lets you take items out of the collection, and doesn't let you put them in (there's no Add method). Therefore it is safe to treat a List<Sheet_1_VM> as an IEnumerable<IPercent>, because there's no danger of anyone putting a Sheet_2_VM in.

This lets us write this:

public interface IPercent
{
    float Percent { get; }
}

public class Sheet_1_VM : IPercent
{
    public float Percent { get; set; }
}

public class Sheet_2_VM : IPercent
{
    public float Percent { get; set; }
}

public class ModelOfExcel_VM : Object
{
    public List<Sheet_1_VM> Sheet1 { get; set; }
    public List<Sheet_2_VM> Sheet2 { get; set; }
}

public static class Program
{
    public static void Main()
    {
        var model = new ModelOfExcel_VM()
        {
            Sheet1 = new List<Sheet_1_VM>()
            {
                new Sheet_1_VM() { Percent = 1.0f },
                new Sheet_1_VM() { Percent = 2.0f },
            },
            Sheet2 = new List<Sheet_2_VM>()
            {
                new Sheet_2_VM() { Percent = 3.0f },
                new Sheet_2_VM() { Percent = 4.0f },
            },
        };

        var pageswithpercent = new List<string>() { "1", "2" }; 

        foreach (var page in pageswithpercent)
        {
            var thisPage = (IEnumerable<IPercent>)model.GetType().GetProperty("Sheet" + page).GetValue(model);

            foreach (var item in thisPage)
            {
                Console.WriteLine(item.Percent);
            }

        }
    }
}

Upvotes: 3

Related Questions