如何为两个集合使用相同的foreach代码?

8

我有两个包含不同类型数据的集合,但它们几乎具有相同的字段集合。 在一个函数中,我需要根据一个条件遍历其中一个集合。 我想只编写一个代码块来覆盖这两种情况。 例如: 我有以下代码:

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

现在,我想简化这段代码,只使用相同的逻辑一次(因为它们是相同的),类似于以下内容(附注:我知道以下代码不正确,我只是解释我想要做什么):
var typecollection = Condition1 ? Type1Collection : Type2Collection;

foreach(var typevar in TypeCollection)
{

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

Type1和Type2的定义类似于以下代码(实际上它们是实体对象):

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

更新1:

我已经包含了一些样本代码,这些代码是我在foreach块内使用的(我正在访问两种类型的公共属性)。

更新2:

我已经包含了Type1和Type2的定义样本,您可以看到这两个类中都有2个公共属性,我希望在foreach块中更新它们。

更新3:

对于混淆造成的困惑,我很抱歉,Type1和Type2都是从EntityObject派生而来的(它们都是我的实体模型的一部分),而Type1Collection和Type2Collection实际上是这两个实体的EntityCollection。


你能在问题中包含两个示例类型定义吗? - Simon
6个回答

9
您可以使用动态类型,但会失去类型安全性。
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());
}

如果它们共享一个公共接口,你可以直接将其转换为该接口。这样可以保持类型安全。
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());
}

虽然我认为你的解决方案很有趣,但我仍然更喜欢编译时安全性。你可以使用协变(使用类型为IEnumerable<object>的变量)而不是为你的示例使用动态编程(其中ToString是来自对象类的方法)。 - Daniel Castro
1
@DanielCastro 我的第二个解决方案具有编译时安全性。 - Simon
仍建议仅用于读取时使用IEnumerable而不是直接使用List。使用List将会使您意外添加不兼容的元素,而您可能只能在运行时才会注意到它。 - Daniel Castro
2
@DanielCastro 我正在使用 IEnumerable。这就是 Cast 返回的内容。我只添加了前面的两个列表以使其成为可编译的代码。 - Simon
我尝试使用您提供的两个示例,但在访问type1和type2的某些公共属性时,它会说它不包含该字段的定义。 - Adel Khayata
我将在问题中更新我在这两个块中使用的一些逻辑。 - Adel Khayata

2

根据Jon Skeet的提示,使用LINQ的Concat方法和OP的陈述,涉及到的类是EntityObject,这里提供另一种可能的解决方案。假设EntityObject子类被定义为partial类:

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

这使得OP可以声明一个接口,其中包含常见的属性,并让他的EntityObject子类实现该接口:
public interface IMyType
{
    String Notes { get; set; }
    decimal Price { get; set; }
}
public partial class Type1 : IMyType {}
public partial class Type2 : IMyType {}

原始代码如下:

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

0

你可以使用存储在字典中的 Predicate 和 Action 来完成它。我建议在这里使用 Action,因为代码片段似乎没有返回任何内容。

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
        }

    }
}

通过这种方式,您的代码的可读性和可测试性将得到提高,并且避免出现过长的方法。 < p > 编辑: < /p >
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
        }
    }
}

我正在使用的类型实际上是实体对象,我认为我不能像你提到的那样修改这些类。 - Adel Khayata
@AdelKhayata,您可以使用扩展方法来解决这个问题,请参考我的更新答案。 - Krishnaswamy Subramanian
在两个不同的方法(“CollectionOneIterator”和“CollectionTwoIterator”)中具有相同的代码并不能解决在两个地方重复使用相同代码的根本问题。 - Edmund Schweppe

0
你可以为type1和type2创建一个基础类型,将这两个类之间的共同属性进行分组:
class MyBaseType {
   // Common properties
}

class Type1 : MyBaseType {
   // Specific properties
}

class Type2 : MyBaseType {
   // Specific properties
}

然后,你可以像这样做:

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

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

编辑: 正如Simon在评论中指出的那样,如果只需要一个接口(即不需要为两种类型都提供具体实现),则应该使用接口而不是基类型。


4
请不要在可以使用接口的情况下使用基本类型。 - Simon
@Simon 没错,我们不确定基类是否适合这种情况,因为我们没有足够的信息。但是,你说得对:如果接口足够了,那就走这条路。 - Daniel Castro

0

这不是一个很好的方法,但至少可以工作。

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

当我尝试使用您的解决方案时,出现以下错误:错误44,foreach语句无法操作类型为'System.Collections.IEnumerable'的变量,因为'System.Collections.IEnumerable'不包含'GetEnumerator'的公共定义。 - Adel Khayata

0

我很惊讶还没有其他人建议一个扩展方法:

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

现在,您的原始类型只需要实现 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; }
}

那么原始代码就变成了:

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

这里根本不需要扩展方法。一旦你有了接口,你可以遍历任何你想要的集合,使用每个值作为接口。当然,我们不知道 OP 是否能够更改实体类型。 - Jon Skeet
True,但扩展方法允许将所有赋值保留在一个地方(以及OP为了可读性而省略的任何其他代码)。 - Edmund Schweppe
2
我更喜欢使用LINQ中的Concat,以便只需一个循环,并且逻辑在一个地方 - 就在你正在进行工作的地方。 - Jon Skeet
我已经更新了问题,说明这2种类型实际上是2个Entity对象,那么这个解决方案在这种情况下仍然有用吗? - Adel Khayata
我甚至没有想到使用 Concat,@JonSkeet。不错! - Edmund Schweppe
@AdelKhayata,我已经发布了另一个解决方案(考虑到Jon Skeet的提示),我相信它更好地回答了你的问题。 - Edmund Schweppe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接