Reputation: 196589
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
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
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
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
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