如何将两个具有相同基类的不同List<T>连接起来?

5

我有一个名为ItemChange<T>的类,它看起来像这样:

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

正如您所看到的,它存储一个对象的两个副本(NewStateOldState)。我使用它来比较字段更改。

现在,我正在尝试让它适用于获取多个对象的更改列表,然后将几种不同类型的 T 列表连接成一个数组,就像这样(注意:Object1Object2 都派生自 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);
}

如您所见,我需要将多种类型的更改连接起来,然后获取最近的更改数量(由numberOfChanges定义)

您有什么建议可以让下面的代码工作吗?Concat 行给出了编译器错误(我假设我必须以某种特殊方式进行转换才能使其工作)。

有没有办法实现这个目标?


你尝试过将 (IEnumerable<ItemChange<MyBase>>)obj2Changes 进行类型转换吗? - Alex
可能是这样的:return obj1Changes.Cast<ItemChange<MyBase>>().Concat(obj2Changes.Cast<ItemChange<MyBase>>()).OrderByDescending(r => r.When).Take(numberofChanges); - Hamlet Hakobyan
4个回答

4

我建议给 ItemChange<T> 添加一个名为 ItemChange 的基类。这个 ItemChange 可以声明 When 属性。这样,将列表内容强制转换为基类 ItemChange 以进行连接和排序就变得非常容易。

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

当然,现在的挑战是弄清楚您的结果过滤列表实际包含什么!我会考虑重构架构以更好地表示业务逻辑。例如,为什么ItemChange<T>甚至需要知道发生了什么变化? - brosell

1
这个怎么样?我不确定,因为现在无法测试。
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);
}

它从ItemChange<Object1>ItemChange<Object2>创建了一个新的ItemChange<MyBase>实例。

根据您的使用情况,您可能希望在Linq的末尾添加.ToList()以提高性能。


1
你正在尝试做的事情并不安全,因为在 ItemChange<Object1>ItemChange<Object2> 之间没有转换,并且肯定没有转换到任意的 ItemChange<T>。你能尝试的最好的方法是 ItemChange<MyBase>,但是在 C# 中类不是协变的,所以这是无效的:

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

因此,您无法将 IEnumerable<ItemChange<Object1>> 强制转换为 IEnumerable<ItemChange<Object2>>

但是,如果为您的 ItemChange<T> 类创建一个接口,则可以安全地执行此操作:

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

然后,您可以将GetChangesGetChangeHistory 方法更改为:

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

0
如果您定义了一个接口 IReadableItemChange<out T>,该接口公开了只读的类型为 T 的属性而不是该类型的字段,并且如果 ItemChange<T> 实现了 IReadableItemChange<T>,那么 ItemChange<Derived1>ItemChange<Derived2> 都将实现 IReadableItemChange<MyBase>(并且,顺便说一句,对于任何 baseType,使得 Derived1:baseTypeDerived2:baseType,也会实现 IReadableItemChange<baseType>)。此外,ItemChange<T> 还可以有一个构造函数,它接受一个 IReadableItemChange<T> 并从中复制数据,这可能也会有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接