leora
leora

Reputation: 196589

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?

Upvotes: 5

Views: 3235

Answers (4)

supercat
supercat

Reputation: 81189

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.

Upvotes: 0

brosell
brosell

Reputation: 101

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

Upvotes: 4

Lee
Lee

Reputation: 144136

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

Upvotes: 1

Alvin Wong
Alvin Wong

Reputation: 12430

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.

Upvotes: 1

Related Questions