【问题标题】:How to combine two types of C# lists into one?如何将两种类型的 C# 列表合二为一?
【发布时间】:2016-08-17 12:06:37
【问题描述】:

我创建了两个列表,分别是 X 和 Y。这两个列表属于不同类型。 (即List<class_A> XList<class_B> Y)。

这两个列表中的值不同。但是这两个列表中都有一个DateTime 字段。

我需要根据date 字段对这些列表进行排序。

我有单独的函数来打印列表 A 和列表 B 的详细信息。

假设排序后的列表看起来像

  • 来自列表 A 的元组,
  • 来自列表 B 的元组,
  • 来自列表 A 的元组,
  • 来自列表 B 的元组,

我的目的是遍历这个列表并调用适当的函数来显示详细信息。即,如果元组来自列表 A,则调用该函数以打印列表 A 的详细信息,反之亦然。

【问题讨论】:

  • 你可以尝试创建一个两个类都实现的接口,然后你可以有一个列表 List<interfaceA> 或者一个基类也可以工作
  • @BojanB 为什么不发布一个简单示例的答案? :)
  • @uteist 我添加了一个示例答案 ;)
  • 是否可以通过使class_Aclass_B实现接口来更改它们的定义,还是必须保持不变?

标签: c# linq ienumerable


【解决方案1】:

您可以创建一个interface 来托管您的常用属性和函数,然后在转换到此接口后加入这些列表并对其进行排序:

interface ICommon
{
    DateTime DT { get; }
    void Print();
}
class ClassA : ICommon
{
    public DateTime DT { get; set; }
    public void Print() { Console.WriteLine("A " + DT); }
}
class ClassB : ICommon
{
    public DateTime DT { get; set; }
    public void Print() { Console.WriteLine("B " + DT); }
}

public static void Main()
{
    var listA = new List<ClassA> { new ClassA() { DT = DateTime.Now.AddDays(-1) }, new ClassA() { DT = DateTime.Now.AddDays(-3) }};
    var listB = new List<ClassB> { new ClassB() { DT = DateTime.Now.AddDays(-2) }, new ClassB() { DT = DateTime.Now.AddDays(-4) }};

    var orderedResult = listA.Cast<ICommon>()
                             .Concat(listB.Cast<ICommon>())
                             .OrderBy(q => q.DT);
    //or possibly even better:
    //var orderedResult = listA.Concat<ICommon>(listB).OrderBy(q => q.DT);
    foreach (var obj in orderedResult)
        obj.Print();
}

【讨论】:

  • suggestion:创建一个列表,你在一个单独的行上 foreach。那里发生了很多事情,这也是您回答的重点。
  • 如果您明确地为Concat 提供通用参数,您可以摆脱两个Cast 调用。 IE。 listA.Concat&lt;ICommon&gt;( listB )。只是节省了一点打字。或者,出于类似原因,您可以放弃对listBCast 调用。
  • @Kyle 你认为对可读性的影响值得那几次击键吗?
  • @slawekwin:为什么您认为删除对.Cast&lt;T&gt; 的不必要调用是“可读性打击”?我个人觉得Kyle的版本更干净(其实我会更进一步明确定义listAlistB开头为IEnumerable&lt;ICommon&gt;。然后代码变成listA.Concat(listB) )
  • 我认为无论哪种方式,可读性都是次要问题。使用 .Cast 的真正问题是您失去了类型安全性,并让自己对 InvalidCastException 保持开放。使用 Concat 是类型安全的,因为如果任一类型不可转换,您将收到编译器错误。
【解决方案2】:

如果您可以更改类 A 和类 B 的定义,那么您应该使用此处的其他答案之一,告诉您为这两个类添加一个接口。

但是,如果您无法更改类 A 和类 B 的定义(可能是因为它们是在您无法更改的库中定义的),那么您必须采用不同的接近。

一个使事情保持整洁的解决方案是引入一个包装类,该类包含A 类或B 类的实例(但不能同时包含两者)。

假设您的类如下所示:

class A
{
    public DateTime Date;
    public double Value;
    public void Print() {}
}

class B
{
    public DateTime Date;
    public string Value;
    public void Print() { }
}

您可以编写如下所示的简单包装器:

class Wrapper
{
    readonly A a;
    readonly B b;

    public Wrapper(A a)
    {
        this.a = a;
    }

    public Wrapper(B b)
    {
        this.b = b;
    }

    public DateTime Date => a?.Date ?? b.Date;

    public void Print()
    {
        if (a != null)
            a.Print();
        else
            b.Print();
    }
}

然后给出两个列表:

var listA = new List<A>();
var listB = new List<B>();

您可以创建Wrapper 对象的排序列表:

var sorted = 
    listA.Select(a => new Wrapper(a))
    .Concat(listB.Select(b => new Wrapper(b)))
    .OrderBy(item => item.Date);

然后为排序列表中的每个项目调用适当的Print()方法:

foreach (var item in sorted)
    item.Print();

【讨论】:

    【解决方案3】:

    如果您根据Base class 创建Class AClass B,则可以让它们从Base class 派生您的DateTime 属性。

    例如:

    public class BaseClass
    {
        public DateTime date {get; set; }
    }
    
    public class ClassA : BaseClass
    {
        //... some other property's
    }
    
    public class ClassB : BaseClass
    {
        //... some other property's
    }
    

    在您的代码中:

    List<BaseClass> a = new List<ClassA>();
    List<BaseClass> b = new List<ClassB>();
    

    并且因为 ClassA 和 ClassB 基于 BaseClass,它们将具有相同的 date 属性。

    【讨论】:

    • 要么使BaseClass抽象(更好)或使用接口(最好)
    【解决方案4】:

    您可以为您拥有的 2 个类创建一个简单的接口:

    public interface interfaceA {
       DateTime TimeStamp { get;set; }
    }
    

    并确保你的两个类都实现了该接口:

    public class class_A : interfaceA {
        public DateTime TimeStamp { get; set; }
        //... Other properties
    }
    
    public class class_B : interfaceA {
        public DateTime TimeStamp { get; set; }
        //... Other properties
    }
    

    在您的主函数/方法中,您创建一个包含两种对象类型的新列表并填充它 - 而不是填充您的 2 个列表之一,您只需填充这个列表:

    var myList = new List<interfaceA>();
    var object = new class_A() { TimeStamp = DateTime.Now };
    myList.Add(object); 
    

    然后排序:

     myList.Sort((a, b) => a.TimeStamp.CompareTo(b.TimeStamp))
    

    我认为这样的事情应该可以工作

    【讨论】:

      【解决方案5】:

      无需创建新的接口或类。您可以同时遍历两个列表并跟踪最近的日期。

      public class TypeA
      {
          public DateTime date { get; set; }
          public void Print() { Console.WriteLine("TypeA: " + date.ToString()); }
      }
      
      public class TypeB
      {
          public DateTime date { get; set; }
          public void Print() { Console.WriteLine("TypeB: " + date.ToString()); }
      }
      
      class Program
      {
          static void Main(string[] args)
          {
              // setup
              var X = new List<TypeA>();
              var Y = new List<TypeB>();
      
              Random rnd = new Random();
              int imin, imax;
      
              imin = rnd.Next(3, 7);
              imax = rnd.Next(10, 20);
      
              for (int i = imin; i < imax; i++)
              {
                  X.Add(new TypeA() { date = DateTime.Now.AddDays(-1 * rnd.Next(1, 1000)) });
              }
      
              imin = rnd.Next(3, 7);
              imax = rnd.Next(10, 20);
      
              for (int i = imin; i < imax; i++)
              {
                  Y.Add(new TypeB() { date = DateTime.Now.AddDays(-1 * rnd.Next(1, 1000)) });
              }
      
              X = X.OrderBy(z => z.date).ToList();
              Y = Y.OrderBy(z => z.date).ToList();
      
              // begin print in order
      
              // index for X list
              int ix = 0;
      
              // index for Y list
              int iy = 0;
      
              // most recently printed date
              DateTime min = DateTime.MinValue;
      
              while (true)
              {
                  if (ix < X.Count && X[ix].date >= min && (iy >= Y.Count || iy < Y.Count && X[ix].date <= Y[iy].date))
                  {
                      X[ix].Print();
                      min = X[ix].date;
                      ix++;
                      continue;
                  }
      
                  if (iy < Y.Count && Y[iy].date >= min)
                  {
                      Y[iy].Print();
                      min = Y[iy].date;
                      iy++;
                  }
      
                  if (ix >= X.Count && iy >= Y.Count)
                  {
                      break;
                  }
              }
          }
      }
      

      样本输出:

      TypeB: 12/19/2013 3:44:44 PM
      TypeB: 2/19/2014 3:44:44 PM
      TypeB: 5/1/2014 3:44:44 PM
      TypeA: 5/27/2014 3:44:44 PM
      TypeA: 6/6/2014 3:44:44 PM
      TypeA: 7/12/2014 3:44:44 PM
      TypeA: 7/18/2014 3:44:44 PM
      TypeB: 12/5/2014 3:44:44 PM
      TypeB: 5/3/2015 3:44:44 PM
      TypeB: 5/4/2015 3:44:44 PM
      TypeB: 8/9/2015 3:44:44 PM
      TypeA: 8/25/2015 3:44:44 PM
      TypeA: 9/20/2015 3:44:44 PM
      TypeB: 9/26/2015 3:44:44 PM
      TypeA: 10/12/2015 3:44:44 PM
      TypeA: 12/7/2015 3:44:44 PM
      TypeB: 12/19/2015 3:44:44 PM
      TypeA: 4/13/2016 3:44:44 PM
      TypeA: 5/23/2016 3:44:44 PM
      TypeB: 8/4/2016 3:44:44 PM
      TypeB: 8/11/2016 3:44:44 PM
      

      【讨论】:

        【解决方案6】:

        在原始 A 和 B 类型不兼容且不可更改的情况下(可能它们来自某些类型不相关的数据库),您还可以使用使用 LINQ 的投影来合并它们。从本质上讲,这创建了第三类,尽管是匿名的。通常,当您可以修改类并使用基类或接口方法时,您应该避免这种方法,但是,由于这并不总是可行,下面是使用 LINQ 投影的示例。请注意,此示例中的原始类型具有不同的日期时间名称,可以在投影期间“合并”。

        class Program
        {
            static void Main(string[] args)
            {
                List<A> aList = new List<A> {new A {OtherAData = "First A", SomeDateTime = DateTime.Parse("12-11-1980")},
                                             new A {OtherAData = "Second A", SomeDateTime = DateTime.Parse("12-11-2000")} };
                List<B> bList = new List<B> {new B {OtherBData = "First B", SomeOtherDateTime = DateTime.Parse("12-11-1990")}, 
                                             new B {OtherBData = "Second B", SomeOtherDateTime = DateTime.Parse("12-11-2010")} };
        
                // create projections
                var unionableA = from a in aList
                    select new {SortDateTime = a.SomeDateTime, AValue = a, BValue = (B) null};
        
                var unionableB = from b in bList
                    select new {SortDateTime = b.SomeOtherDateTime, AValue = (A) null, BValue = b};
        
                // union the two projections and sort
                var union = unionableA.Union(unionableB).OrderBy(u => u.SortDateTime);
        
                foreach (var u in union)
                {
                    if (u.AValue != null)
                    {
                        Console.WriteLine("A: {0}",u.AValue);
                    }
                    else if (u.BValue != null)
                    {
                        Console.WriteLine("B: {0}",u.BValue);
                    }
                }
        
            }
        }
        
        public class A
        {
            public DateTime SomeDateTime { get; set; }
            public string OtherAData { get; set; }
            public override string ToString()
            {
                return string.Format("SomeDateTime: {0}, OtherAData: {1}", SomeDateTime, OtherAData);
            }
        }
        
        public class B
        {
            public DateTime SomeOtherDateTime { get; set; }
            public string OtherBData { get; set; }
            public override string ToString()
            {
                return string.Format("SomeOtherDateTime: {0}, OtherBData: {1}", SomeOtherDateTime, OtherBData);
            }
        }
        

        样本输出:

         A: SomeDateTime: 12/11/1980 12:00:00 AM, OtherAData: First A
         B: SomeOtherDateTime: 12/11/1990 12:00:00 AM, OtherBData: First B
         A: SomeDateTime: 12/11/2000 12:00:00 AM, OtherAData: Second A
         B: SomeOtherDateTime: 12/11/2010 12:00:00 AM, OtherBData: Second B
        

        【讨论】:

        • "(from a in unionableA select a)"... 为什么需要这个?
        • 你是对的,而且,from b 部分也是不必要的。我将更新示例并删除多余的代码。谢谢!
        • 在这种情况下union 是否有意义,而不仅仅是concat
        • 在这种情况下,union 和 concat 的行为是相同的,因为它会进行引用比较并且对象永远不会相同。使用 concat 可能会稍微提高性能,但您当然不会在此示例中看到它。
        【解决方案7】:

        嗯,除了接口解决方案,你可以使用抽象类,这个类可以包含方法 Print 的默认实现:

        abstract class AbstractClass
        {
            protected string Details { get; set; }
            public DateTime DT { get; set; }
        
            private AbstractClass() { }
        
            protected AbstractClass(string details) { Details = details;}
        
            public virtual void Print()
            {
                Console.WriteLine($"{Details} {DT}");
            }
        }
        
        internal class ClassA : AbstractClass
        {
            public ClassA() : base("A") { }
        }
        
        class ClassB : AbstractClass
        {
            public ClassB() : base("B") { }
        }
        

        用法:

        var commonList = listA.Concat(listB.Cast<AbstractClass>()).OrderBy(e => e.DT).ToList();
        
        commonList.ForEach(obj => obj.Print());
        

        【讨论】:

          【解决方案8】:

          如果您不想创建基本类型或接口,可以执行以下操作:

          new List<IEnumerable<dynamic>> { listA, listB }
              .SelectMany(x => x)
              .OrderBy(x => x.Date).ToList()
              .ForEach(x => Print(x));
          

          您显然需要一个 Print() 函数,为 class_Aclass_B 重载。

          Print() 设为成员函数或扩展方法将使上述代码更加整洁。

          【讨论】:

            【解决方案9】:

            创建具有其他两个属性的第三个类。然后创建其对象列表并按日期时间对其进行排序。

            【讨论】:

            • 一个接口就够了,不需要使用第三类。
            【解决方案10】:

            我建议使用带有Action 委托的匿名类型更简单的方法。 无需更改 A 和 B 的定义,无需引入额外的类/接口。

            class A
            {
                public DateTime Date;
                public void Print() { Console.Write("A"); }
            }
            
            class B
            {
                public DateTime Date;
                public void Print() { Console.Write("B"); }
            }
            
            class Program
            {
                static void Main(string[] args)
                {
                    // Generate sample data
                    var r = new Random();
            
                    var listA = Enumerable.Repeat(0, 10)
                        .Select(_ => new A { Date = new DateTime(r.Next()) })
                        .ToList();
            
                    var listB = Enumerable.Repeat(0, 10)
                        .Select(_ => new B { Date = new DateTime(r.Next()) })
                        .ToList();
            
            
                    // Combine the two lists into one of an anonymous type
                    var combined = listA.Select(a => new { a.Date, Print = new Action(() => a.Print()) })
                        .Concat(listB.Select(b => new { b.Date, Print = new Action(() => b.Print()) }))
                        .OrderBy(c => c.Date)
                        ;
            
                    foreach (var item in combined)
                    {
                        item.Print();
                        Console.Write(": " + item.Date);
                        Console.WriteLine();
                    }
                }
            }
            

            【讨论】:

              【解决方案11】:

              如果我解决了问题,那么我会使用这样的代理类:

                  public class Proxy
                  {
                      public object obj { get; set; }
                      public DateTime date { get; set; }
                  }
              
                  var listAll = listA.Select(a => new Proxy { obj = a, date = a.date }).Union(listB.Select(b => new Proxy { obj = b, date = b.date })).OrderBy(d=>d.date).ToList();
              
                  listAll.ForEach(c => 
                     {
                          if (c.obj.GetType() == typeof(A))
                              printA(((A)c.obj));
                          else if (c.obj.GetType() == typeof(B))
                              printB(((B)c.obj));
              
                     }
              

              但我宁愿让打印方法来决定谁是谁

              【讨论】:

                【解决方案12】:

                首先,按其属性排序第一个,然后将其转换为对象,然后将其与按其属性排序的第二个列表连接:

                var lists = listA
                .OrderBy(x => x.DateTimeProp)
                .OfType<object>()
                .Concat(
                  listB
                  .OrderBy(x => x.DateTimeProp)
                  .OfType<object>()
                );
                

                在此之后,您可以遍历所有项目并根据每个项目的类型进行操作。 当然,这会对每个列表进行独立排序。如果您需要全局排序,则需要一个基类或一个公开可排序属性的共享接口。

                【讨论】:

                • 顺便说一句,这不起作用。看看排序列表应该是什么样的问题。也许listA.Cast&lt;dynamic&gt;().Concat(listB.Cast&lt;dynamic&gt;()).OrderBy(x =&gt; x.DateTimeProp) 可能有用。
                • 那不只是创建一个列表,其中所有列表 A 按日期顺序,然后是所有列表 B 按日期顺序?据我了解,需要的是按日期顺序排序的列表 A 和 B 的组合列表。
                • 当对象列表返回为Object's 时,如何将对象转换回相应的类?我现在可能想多了
                • @PaulF:你说得对,我的解决方案是对两者进行独立排序。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-10-19
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-04-04
                相关资源
                最近更新 更多