Как объединить два типа списков С# в один?

Я создал два списка: X и Y. Эти два списка имеют разные типы. (т.е. List<class_A> X и List<class_B> Y).

Значения в обоих этих списках различны. Но в обоих этих списках есть поле DateTime.

Мне нужно отсортировать эти списки в соответствии с полем date.

У меня есть отдельные функции для печати деталей списка A и списка B.

Предположим, что отсортированный список выглядит как

  • кортеж из списка A,
  • кортеж из списка B,
  • кортеж из списка A,
  • кортеж из списка B,

Моя цель - пропустить этот список и вызвать соответствующую функцию для отображения деталей. т.е. если кортеж из списка A, то вызовите функцию для печати списка A деталей и наоборот.

Ответы

Ответ 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();
}

Ответ 2

Если вы можете изменить определение класса A и class B, тогда вы должны использовать один из других ответов здесь, в котором говорится, что вы добавляете интерфейс для обоих этих классов. p >

Однако , если вы не можете изменить определение класса A и class B (возможно, потому, что они определены в библиотеке, которую вы не можете изменить), тогда вы должны принять другое подход.

Одним из решений, которое держит вещи довольно аккуратно, является введение класса-оболочки, который содержит либо экземпляр класса A, либо class 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

Вы можете сделать простой интерфейс для 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))

Я думаю, что что-то вроде этого должно работать

Ответ 4

Если вы создаете Class A и Class B на основе Base class, вы можете заставить их получить свойство DateTime из Base class.

например:

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.

Ответ 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

Хорошо, кроме решения интерфейса, вы можете использовать абстрактный класс, и этот класс может содержать стандартную реализацию метода 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());

Ответ 7

В случае, когда исходные типы A и B несовместимы и неизменяемы (возможно, они из какой-либо базы данных, где типы не связаны), вы также можете использовать проекцию с использованием LINQ для их объединения. Это, в сущности, создает третий класс, пусть и анонимный. В общем, вам следует избегать такого подхода, когда вы можете модифицировать классы и использовать подход базового класса или интерфейса, но, поскольку это не всегда возможно, ниже приведен пример использования проекции LINQ. Обратите внимание, что исходные типы в этом примере имеют разные имена для datetime, которые могут быть "объединены" во время проекции.

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

Ответ 8

Если вы не хотите создавать базовый тип или интерфейс, вы можете сделать что-то вроде этого:

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

Вам, очевидно, понадобится функция Print(), перегруженная для class_A и class_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>()
);

После этого вы можете выполнять итерацию по всем элементам и действовать в соответствии с каждым типом элемента. Конечно, это сортирует каждый список самостоятельно. Если вам нужно сортировать глобально, вам либо нужен базовый класс, либо общий интерфейс, который предоставляет свойство сортировки.