web-dev-qa-db-fra.com

Comment combiner deux types de listes C # en un seul?

J'ai créé deux listes dites X & Y. Ces deux listes sont de types différents. (ie List<class_A> X & List<class_B> Y).

Les valeurs dans ces deux listes sont différentes. Mais il existe un champ DateTime dans ces deux listes.

Je dois trier ces listes en fonction du champ date.

J'ai différentes fonctions pour imprimer les détails de la liste A et de la liste B.

Supposons que la liste triée ressemble à

  • Tuple de la liste A,
  • Tuple de la liste B,
  • Tuple de la liste A,
  • Tuple de la liste B,

Mon but est de parcourir cette liste en boucle et d’appeler la fonction appropriée pour afficher les détails. c'est-à-dire que si le tuple provient de la liste A, appelez la fonction d'impression des détails de la liste A et inversement.

15
Shafeek

Vous pouvez créer une interface pour héberger vos propriétés et fonctions communes, puis joindre ces listes après la conversion sur cette interface et la trier ensuite:

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();
}
27
slawekwin

Si vous pouvez modifier la définition de la classe A et de la classe B, vous devez utiliser l'une des autres réponses ci-dessous, qui vous invite à ajouter une interface à ces deux classes.

Toutefois, si vous ne pouvez pas modifier la définition de la classe A et de la classe B (peut-être parce qu'elles sont définies dans une bibliothèque que vous ne pouvez pas modifier), vous devez adopter une approche différente.

Une solution qui garde les choses bien ordonnées consiste à introduire une classe wrapper qui contient une instance de la classe A ou de la classe B (mais pas les deux).

Supposons que vos classes ressemblent à ceci:

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

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

Vous pouvez écrire un simple wrapper qui ressemble à ceci:

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

Puis donné deux listes:

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

Vous pouvez créer une liste triée d'objets Wrapper:

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

Ensuite, pour appeler la méthode Print() appropriée pour chaque élément de la liste triée:

foreach (var item in sorted)
    item.Print();
21
Matthew Watson

Vous pouvez créer une interface simple pour les 2 classes que vous avez: 

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

Et assurez-vous que vos deux classes implémentent cette interface: 

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

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

dans votre fonction/méthode principale, vous créez une nouvelle liste qui contient les deux types d’objets et vous la remplissez. Au lieu de remplir l’une de vos deux listes, vous ne remplissez que cette liste: 

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

Et puis le trier: 

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

Je pense que quelque chose comme ça devrait marcher

6
Bojan B

Si vous créez vos Class A et Class B sur la base d'un Base class, vous pouvez leur demander de dériver votre propriété DateTime à partir de Base class.

par exemple:

public class BaseClass
{
    public DateTime date {get; set; }
}

public class ClassA : BaseClass
{
    //... some other property's
}

public class ClassB : BaseClass
{
    //... some other property's
}

dans votre code:

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

Etant donné que ClassA et ClassB sont basées sur BaseClass, elles auront la même propriété date

6
DDD Soft

Pas besoin de créer de nouvelles interfaces ou classes. Vous pouvez simplement parcourir les deux listes en même temps et suivre la date la plus récente. 

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

Exemple de sortie:

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
5
BurnsBA

Dans le cas où les types A et B d'origine sont incompatibles et non modifiables (ils proviennent peut-être d'une base de données où les types ne sont pas liés), vous pouvez également utiliser une projection utilisant LINQ pour les fusionner. Cela crée, en substance, une troisième classe, même anonyme. En général, vous devriez éviter cette approche lorsque vous pouvez modifier les classes et utiliser l'approche de classe ou d'interface de base. Toutefois, comme cela n'est pas toujours possible, voici un exemple d'utilisation d'une projection LINQ. Notez que les types originaux dans cet exemple ont des noms différents pour le datetime qui peuvent être "fusionnés" pendant la projection.

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

Exemple de sortie:

 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
2
Reginald Blue

En dehors de la solution d’interface, vous pouvez utiliser une classe abstraite et cette classe peut contenir l’implémentation par défaut de la méthode 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") { }
}

Utilisation:

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

commonList.ForEach(obj => obj.Print());
1
Fabjan

Si vous ne voulez pas créer un type de base ou une interface, vous pouvez faire quelque chose comme ceci:

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

Vous aurez évidemment besoin d’une fonction Print(), surchargée pour class_A et class_B.

Faire de Print() une fonction membre ou une méthode d'extension rendrait le code ci-dessus encore plus net.

1
eoinmullan

Créez une troisième classe ayant les propriétés des deux autres. Créez ensuite une liste de ses objets et triez-la par date/heure. 

0
Mahdi

si le problème est résolu correctement, j'utiliserais une classe de proxy comme celle-ci:

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

       }

mais je préférerais laisser la méthode d'impression décider qui est qui

0
vlscanner

Je suggérerais une manière plus simple d'utiliser un type anonyme avec un délégué Action. Pas besoin de changer la définition de A et B, pas besoin d'introduire des classes/interfaces supplémentaires.

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();
        }
    }
}
0
Ripple