Home > Blockchain >  How to make nested foreach loop method generic
How to make nested foreach loop method generic

Time:12-30

I want to get a list of all (unique) Apple (or Oranges) objects:

var theBigFruitsList = new List<Fruits>{
    new Fruits(){
        Apples = new List<Apple>{
                new Apple { Id = 1 },
                new Apple { Id = 2 }
            }
        },
        Oranges = new List<Orange>{
                new Orange { Id = 4 },
                new Orange { Id = 5 }
            }
        },
        FruitBoolean = False,
        FruitCount = 4,
    },
    new Fruits(){
        Apples = new List<Apple>{
                new Apple { Id = 3 },
                new Apple { Id = 1 },
            }
        },
        Oranges = new List<Orange>{
                new Orange { Id = 6 },
            }
        }
        FruitBoolean = False,
        FruitCount = 3,
    }
}

I have written this method for it:

public static List<Apple> GetApplesFromBigFruitsList(List<Fruits> theBigFruitsList )
{
    var dc = new Dictionary<long, Apple>();
    foreach (var fruits in theBigFruitsList)
    {
        foreach (var apple in fruits.Apples)
        {
            if (!dc.ContainsKey(apple.Id))
                dc.Add(apple.Id, apple);
        }
    }
    return dc.Values.ToList();
}

But as besides Apples and Oranges there are many other types of 'Fruits' in that object, I have that method more then 10 times where the word Apple is just replaced with Orange.. It would make sense to make it generic.

I wrote this function but it gives an error as Fruits class does not implement an enumerator. Please help!

    public static List<T> FilterFruits<T>(T typeToGet, List<Fruits> theBigFruitsList)
    {
        List<T> fruitResult = new List<T>();

        var fruitType = typeToGet.GetType();

        foreach (var fruits in theBigFruitsList)
        {
            foreach (var fruit in fruits) //errors, intention is to loop over all properties in the Fruits entity to find one specific type
                if (fruit.GetType() == fruitType) //check if type is Apple
                {
                    fruitResult.AddRange(fruits); //add the Apples to a list
                }
        }
        return fruitResult;
    }

The classes:

public class Fruits{
    public List<Apple> Apples { get; set; }
    public List<Orange> Oranges { get; set; }
    public List<Pineapple> Pineapples { get; set; }
    public List<Mango> Mangos { get; set; }
    public bool FruitBoolean { get; set; }
    public long FruitCount { get; set; }
    }
public class Apple{
    public long Id { get; set; }
}
public class Orange{
    public long Id { get; set; }
}   
public class Pineapple{
    public long Id { get; set; }
}   
public class Mango{
    public long Id { get; set; }
}   

The desired method result:

var Apples = List<Apple>{
    new Apple { Id = 1 },
    new Apple { Id = 2 },
    new Apple { Id = 3 }
}

CodePudding user response:

With One Big List

its separate list is...weird. I suggest you combine them in a single list. If you can't change the design, you can combine them at run time like this:

IEnumerable<object> GetAllFruits(Fruits bigFruitlist)
{
    return ((IEnumerable<object>)bigFruitlist.Apples) 
    .Concat((IEnumerable<object>)bigFruitlist.Oranges) 
    .Concat((IEnumerable<object>)bigFruitlist.Mangoes) 
    .Concat((IEnumerable<object>)bigFruitlist.Pineapples); 
}

Of course it would be way better if all of your fruits had a common interface-- then you wouldn't need IEnumerable<object>-- but this can still work if you can't make that change either.

Once you have the combined list, the rest is easy:

List<T> FilterFruits<T>(Fruits bigFruitlist)
{
    return GetAllFruits(bigFruitList).OfType<T>().ToList();
}

With an Array of Lists

If there is some reason you want to avoid enumerating all of the lists (i.e. the lists are massive and performance is a concern), you can also do it with a list of lists.

object[] GetAllFruitLists(Fruits bigFruitlist)
{
    return new object[]
    {
        bigFruitlist.Apples,
        bigFruitlist.Oranges,
        bigFruitlist.Mangoes, 
        bigFruitlist.Pineapples
    }; 
}

List<T> FilterFruits<T>(Fruits bigFruitlist)
{
    return GetAllFruitLists(bigFruitList).OfType<List<T>>();
}

CodePudding user response:

To interrogate an object's type at runtime use Reflection. Like this:

public static List<T> FilterFruits<T>(List<Fruits> fruitsList) where T : IFruit
{
    List<T> fruitResult = new List<T>();

    var fruitType = typeof(T);

    foreach (var fruits in fruitsList)
    {
        foreach (var fp in typeof(Fruits).GetProperties())
        {
            if (fp.PropertyType == typeof(List<T>)) //check if type is Apple
            {
                fruitResult.AddRange((List<T>)(object)fp.GetValue(fruits)); //add the Apples to a list
            }
        }
    }
    return fruitResult;
} 

To do this without Reflection (which can be too slow for some scenarios), you can do something like this:

public static List<T> GetDistinct<T>( IEnumerable<Fruits> fruitsList) where T : IFruit
{
    var ft = typeof(T);
    Func<Fruits, List<T>> picker;

    if (ft == typeof(Apple))
    {
        picker = (fruits) => (List<T>)(object)fruits.Apples;
    }
    else if (ft == typeof(Mango))
    {
        picker = (fruits) => (List<T>)(object)fruits.Mangos;
    }
    else
    {
        throw new NotImplementedException($"Fruit Type {ft.Name} not supported");
    }

    var rv = new Dictionary<long, T>();
    foreach (var t in fruitsList.SelectMany(picker))
    {
        if (!rv.ContainsKey(t.Id))
        {
            rv.Add(t.Id, t);
        }
    }

    return rv.Values.ToList();


}
  • Related