Adel Khayata
Adel Khayata

Reputation: 2836

How to use the same foreach code for 2 collections?

I have 2 collections of 2 different types but have almost the same set of fields. in one function, I need to iterate through one of the collections depending on one condition. I want to write only one code block that will cover both cases. Example: I have the following code:

if (condition1)
{
    foreach(var type1var in Type1Collection)
    {

    // Do some code here
       type1var.Notes = "note";
       type1var.Price = 1;
    }
}
else
{
    foreach(var type2var in Type2Collection)
    {

    // the same code logic is used here
       type2var.Notes = "note";        
       type2var.Price = 1;
    }
}

Now: I want to simplify this code to use the same logic only once ( as they are identical ), something like the following ( P.S : I know the following code is not correct, I am just explaining what I want to do ):

var typecollection = Condition1 ? Type1Collection : Type2Collection;

foreach(var typevar in TypeCollection)
{

   // the same code logic is used here
   typevar.Notes = "note";
   typevar.Price = 1;       
 }

The definition of Type1 & Type2 is similar to the following code ( Actually they are Entity objects):

    public class Type1 : EntityObject
    {
        public int Type1ID { get; set; }
        public int Type1MasterID { get; set; }

        public String Notes { get; set; }
        public decimal Price { get; set; }
    }

    public class Type2 : EntityObject
    {
        public int Type2ID { get; set; }
        public int Type2MasterID { get; set; }

        public String Notes { get; set; }
        public decimal Price { get; set; }
    }

Update 1:

I have included some sample code I am using inside foreach block ( I am accessing a public properties of the 2 types).

Update 2:

I have included sample Type1 & Type2 definitions, as you can see I have 2 common Public Properties in both classes which I want to update in foreach block.

Update 3:

I am sorry for the confusion, Type1 & Type2 are derived from EntityObject ( They are both part of my entity model, and the Type1Collection & Type2Collection are actually EntityCollection of these 2 entities.

Upvotes: 8

Views: 727

Answers (6)

Edmund Schweppe
Edmund Schweppe

Reputation: 5132

Given Jon Skeet's hint of using LINQ's Concat method and the OP's statement that the classes involved are EntityObjects, here's another possible solution. This assumes that the EntityObject subclasses are defined as partial classes:

public partial class Type1 : EntityObject
{
    public int Type1ID { get; set; }
    public int Type1MasterID { get; set; }
    public String Notes { get; set; }
    public decimal Price { get; set; }
}

public partial class Type2 : EntityObject
{
    public int Type2ID { get; set; }
    public int Type2MasterID { get; set; }

    public String Notes { get; set; }
    public decimal Price { get; set; }
}

This allows the OP to declare an interface with the common properties, and have his EntityObject subclasses implement that interface:

public interface IMyType
{
    String Notes { get; set; }
    decimal Price { get; set; }
}
public partial class Type1 : IMyType {}
public partial class Type2 : IMyType {}

And the original code becomes:

var query = (
    from type1var in type1Collection
    where condition1
    select (IMyType)type1var
   ).Concat(
    from type2var in type2Collection
    where !condition1
    select (IMyType)type2var
   );
foreach(var myType in query)
{
    myType.Notes = "note";
    myType.Price = 1;
}

Upvotes: 2

You can do it with Predicate and Action stored in a Dictionary. I am suggesting Action here since the code snippet doesn't seems to return anything

public class IterationExample
{
    private readonly Dictionary<bool, Action> dictionary;

    public IterationExample()
    {
        dictionary = new Dictionary<bool, Action> { { true, CollectionOneIterator }, { false, CollectionTwoIterator } };
    }

    public void PublicMethod()
    {
        dictionary[condition]();
    }

    private void CollectionOneIterator()
    {
        foreach (var loopVariable in Type1Collection)
        {
            //Your code here
        }
    }

    private void CollectionTwoIterator()
    {
        foreach (var loopVariable in Type2Collection)
        {
            //Your code here
        }

    }
}

With this way the readbility and testability of your code improves and also avoids long methods.

Edit:

public class Entity
{
    public IList<string> Type1Collection { get; set; }
    public IList<string> Type2Collection { get; set; } 
}

public class ConsumingClass
{
    public void Example()
    {
        var entity = new Entity();
        entity.PublicMethod();
    }
}

public static class IterationExample
{
    private static readonly Dictionary<bool, Action<Entity>> dictionary;

    static IterationExample()
    {
        dictionary = new Dictionary<bool, Action<Entity>> { { true, CollectionOneIterator }, { false, CollectionTwoIterator } };
    }

    public static void PublicMethod(this Entity entity)
    {
        dictionary[condition]();
    }

    private static void CollectionOneIterator(Entity entity)
    {
        foreach (var loopVariable in entity.Type1Collection)
        {
            //Your code here
        }
    }

    private static void CollectionTwoIterator(Entity entity)
    {
        foreach (var loopVariable in entity.Type2Collection)
        {
            //Your code here
        }
    }
}

Upvotes: 0

Simon
Simon

Reputation: 34880

You could use dynamic. Note you will lose type safety.

var list1 = new List<bool>(){true,false};
var list2 = new List<int>(){1,2};

var typecollection = condition1 ? list1.Cast<dynamic>() : list2.Cast<dynamic>();
foreach (var value in typecollection)
{
    //then you can call a method you know they both have
    Debug.WriteLine(value.ToString());
}

Or if they share a common interface you can cast directly to that. You will maintain type safety

var list1 = new List<bool>(){true,false};
var list2 = new List<int>(){1,2};

var typecollection = condition1 ? list1.Cast<IConvertible>() : list2.Cast<IConvertible>();
foreach (IConvertible convertible in typecollection)
{
    //we now know they have a common interface so we can call a common method
    Debug.WriteLine(convertible.ToString());
}

Upvotes: 9

Edmund Schweppe
Edmund Schweppe

Reputation: 5132

I'm surprised nobody else has suggested an extension method yet:

public interface IMyType
{
    String Notes { get; set; }
    decimal Price { get; set; }
}

public static class MyTypeExtensions
{
    public static void MyLogic(this IMyType myType)
    {
        // whatever other logic is needed
        myType.Notes = "notes";
        myType.Price = 1;
    }
 }

Now, your original types just need to implement IMyType:

public class Type1 : IMyType
{
    public int Type1ID { get; set; }
    public int Type1MasterID { get; set; }

    public String Notes { get; set; }
    public decimal Price { get; set; }
}

public class Type2 : IMyType
{
    public int Type2ID { get; set; }
    public int Type2MasterID { get; set; }

    public String Notes { get; set; }
    public decimal Price { get; set; }
}

Then the original code becomes:

if (condition1)
{
    foreach (var type1 in type1Collection)
    {
        type1.MyLogic();
    }
}
else
{
    foreach (var type2 in type2Collection)
    {
        type2.MyLogic();
    }
}

Upvotes: 0

Alexander Forbes-Reed
Alexander Forbes-Reed

Reputation: 2975

This is not a very nice way to do it, but it would atleast work.

        var type1Collection = new Collection<Type1>();
        var type2Collection = new Collection<Type2>();

        var condition1 = new Random().Next(0, 2) != 0;

        dynamic selectedCollection;
        if (condition1)
            selectedCollection = type1Collection;
        else
            selectedCollection = type2Collection;

        foreach (var typeVar in selectedCollection)
        {
            typeVar.Notes = "note";
            typeVar.Price = 1;
        }

Upvotes: 0

Daniel Castro
Daniel Castro

Reputation: 1290

You could create a base type for type1 and type2 that groups the common properties between the two classes:

class MyBaseType {
   // Common properties
}

class Type1 : MyBaseType {
   // Specific properties
}

class Type2 : MyBaseType {
   // Specific properties
}

Then, you could do something like this:

IEnumerable<MyBaseType> collection;
if(condition1)
   collection = type1Collection;
else
   collection = type2Collection;

foreach(MyBaseType element in collection) {
   // Common logic
}

EDIT: As Simon points out in the comments, you should use an interface instead of a base type if it's enough (i.e you don't need a specific implementation for both types).

Upvotes: 0

Related Questions