【问题标题】:How to use the same foreach code for 2 collections?如何为 2 个集合使用相同的 foreach 代码?
【发布时间】:2013-01-10 03:22:40
【问题描述】:

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

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

现在:我想简化此代码以仅使用一次相同的逻辑(因为它们是相同的),如下所示(PS:我知道以下代码不正确,我只是在解释我想要做什么):

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 的定义类似如下代码(其实都是Entity对象):

    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 种类型的公共属性)。

更新 2:

我已包含示例 Type1 和 Type2 定义,如您所见,我想在 foreach 块中更新这两个类中的 2 个公共公共属性。

更新 3:

对不起,Type1 和 Type2 是从 EntityObject 派生的(它们都是我的实体模型的一部分,Type1Collection 和 Type2Collection 实际上是这两个实体的 EntityCollection。

【问题讨论】:

  • 能否包含两个有问题的样本类型定义

标签: c# entity-framework


【解决方案1】:

您可以使用动态。请注意,您将失去类型安全性。

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) 类型的变量
  • @DanielCastro 我的第二个解决方案具有编译时安全性
  • 如果唯一的目的是阅读,仍然会建议使用 IEnumerable 而不是直接使用 List。使用 List 会允许您意外添加不兼容的元素,并且您可能会在运行时才注意到它。
  • @DanielCastro 我正在使用 IEnumerable。这就是 Cast 的回报。我只在顶部添加了两个列表以使其成为可编译代码。
  • 我已经尝试使用您提供的两个示例,但是在这两种情况下,当我从 type1 和 type2 访问一些公共属性时,都表示它不包含该字段的定义。
【解决方案2】:

鉴于 Jon Skeet 暗示使用 LINQ 的 Concat 方法和 OP 声明所涉及的类是 EntityObjects,这是另一种可能的解决方案。这假设 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;
}

【讨论】:

    【解决方案3】:

    您可以为 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 在 cmets 中指出的那样,如果足够的话,您应该使用接口而不是基类型(即您不需要两种类型的特定实现)。

    【讨论】:

    • 当接口足够时请不要使用基本类型
    • @Simon 对,只是我们不确定基类是否适合这种情况,因为我们没有足够的信息。但是,再一次,你是对的:如果一个界面就足够了,那就是要走的路
    【解决方案4】:

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

            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' 的定义
    【解决方案5】:

    我很惊讶还没有人提出扩展方法:

    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是否能够改变实体类型。
    • 是的,但扩展方法允许将所有分配保存在一个地方(以及 OP 为了可读性而省略的任何其他代码)。
    • 我已经更新了这两种类型实际上是 2 EntityObjects 的问题,这个解决方案在这种情况下仍然有用吗?
    • 我什至没有想过使用 Concat, @JonSkeet 。不错!
    【解决方案6】:

    您可以使用存储在字典中的谓词和动作来做到这一点。我建议在这里采取行动,因为代码 sn-p 似乎没有返回任何内容

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

    通过这种方式,您的代码的可读性和可测试性得到提高,同时也避免了冗长的方法。

    编辑:

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

    【讨论】:

    • 我使用的类型实际上是实体对象,我相信我不能像你提到的那样修改修改类。
    • @AdelKhayata 您可以使用扩展方法来克服这个问题,请参考我的更新答案
    • 在两个不同的方法(CollectionOneIteratorCollectionTwoIterator)中使用相同的代码并不能解决在两个地方重复相同代码的原始问题。
    猜你喜欢
    • 2013-03-26
    • 2017-05-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 2015-04-24
    相关资源
    最近更新 更多