简体   繁体   中英

How can i concatenate two different list<T> where both have same base class?

I have a class called ItemChange<T> that looks like this:

public class ItemChange<T> where T : MyBase
{
    public DateTime When { get; set; }
    public string Who { get; set; }
    public T NewState;
    public T OldState;
}

As you can see it stores two copies of an object ( NewState and OldState ). I use this to compare field changes.

I now am trying to get this to work where I get a list of changes across multiple objects and then concatenate a few different types of T list into one array like this (NOTE: both Object1 and Object2 derive from MyBase :

public IEnumerable<ItemChange<T>> GetChangeHistory<T>(int numberOfChanges) where T : MyBase
{
    IEnumerable<ItemChange<Object1>> obj1Changes= GetChanges<Object1>();
    IEnumerable<ItemChange<Object2>> obj1Changes= GetChanges<Object2>();
    return obj1Changes.Concat(obj2Changes).OrderByDescending(r => r.When).Take(numberofChanges);
}

As you can see I need to concatenate changes from multiple types but then I want to grab the most recent number of changes (defined by numberOfChanges )

Any suggestions for how I could get the below to work as the Concat line gives me a compiler error (I assume I have to cast in some special way to get this to work).

Is there any way to do that?

I recommend adding a base class to ItemChange<T> called ItemChange . The ItemChange can declare the When property. Then it becomes pretty easy to cast the list contents to the base ItemChange for concat and order by.

using System;
using System.Collections.Generic;
using System.Linq;

namespace brosell
{

public class MyBase
{

}

public class ItemChange
{
    public DateTime When { get; set; }

}

public class ItemChange<T>: ItemChange where T : MyBase
{
    public string Who { get; set; }
    public T NewState;
    public T OldState;
}

public class Sub1: MyBase
{

}

public class Sub2: MyBase
{

}

   public class HelloWorld
   {
    public static void Main(string[] args)
    {
        List<ItemChange<Sub1>> listOfSub1 = new List<ItemChange<Sub1>>();
        List<ItemChange<Sub2>> listOfSub2 = new List<ItemChange<Sub2>>();

        var concated = listOfSub1.Cast<ItemChange>().Concat(listOfSub2.Cast<ItemChange>());

        var filtered = concated.OrderByDescending(ic => ic.When).Take(10);  

        Console.WriteLine("{0}", filtered.Count());
        }
   } 
}

How about this? I am not sure because I cannot test it now.

public IEnumerable<ItemChange<T>> GetChangeHistory<T>(int numberOfChanges) where T : MyBase
{
    IEnumerable<ItemChange<MyBase>> obj1Changes = GetChanges<Object1>().Select(i => new ItemChange<MyBase>(){ When = i.When, Who = i.Who, NewState = i.NewState, OldState = i.OldState });
    IEnumerable<ItemChange<MyBase>> obj1Changes = GetChanges<Object2>().Select(i => new ItemChange<MyBase>(){ When = i.When, Who = i.Who, NewState = i.NewState, OldState = i.OldState });
    return obj1Changes.Concat(obj2Changes).OrderByDescending(r => r.When).Take(numberofChanges);
}

It creates a new instance of ItemChange<MyBase> from ItemChange<Object1> or ItemChange<Object2> .

Depending on your usage, you may want to add .ToList() to the end of the Linq to increase performance.

What you are trying to do is not safe since there is no conversion between an ItemChange<Object1> and an ItemChange<Object2> , and there is certainly no conversion to some arbitrary ItemChange<T> . The best you can try to do is ItemChange<MyBase> but classes are not covariant in C# so this is not valid:

ItemChange<MyBase> change = new ItemChange<Object1>();

You therefore cannot cast an IEnumerable<ItemChange<Object1>> to an IEnumerable<ItemChange<Object2>> .

However if you create an interface for your ItemChange<T> class then you can do it safely:

public interface IItemChange<out T> where T : MyBase
{
    DateTime When { get; set; }
    string Who { get; set; }
    T NewState { get; }
    T OldState { get; }
}

public class ItemChange<T> : IItemChange<T> where T : MyBase
{
    public DateTime When { get; set; }
    public string Who { get; set; }
    public T NewState { get; set; }
    public T OldState { get; set; }
}

You can then change your GetChanges and GetChangeHistory methods to:

private static IEnumerable<IItemChange<T>> GetChanges<T>() where T : MyBase { ... }

public static IEnumerable<IItemChange<MyBase>> GetChangeHistory(int numberOfChanges)
{
    IEnumerable<IItemChange<MyBase>> obj1Changes = GetChanges<Object1>();
    IEnumerable<IItemChange<MyBase>> obj2Changes = GetChanges<Object2>();
    return obj1Changes.Concat(obj2Changes).OrderByDescending(r => r.When).Take(numberOfChanges);
}

If you defined an interface IReadableItemChange<out T> which exposed read-only properties of type T rather than fields of that type, and if ItemChange<T> implemented IReadableItemChange<T> , then both ItemChange<Derived1> and ItemChange<Derived2> would implement IReadableItemChange<MyBase> (and, incidentally, IReadableItemChange<baseType> for any baseType such that Derived1:baseType and Derived2:baseType ). It may be helpful as well for ItemChange<T> to have a constructor which accepts an IReadableItemChange<T> and copies the data from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM