如何正确转换集合?

How to transform collections correctly?

class Anime
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string ImageUrl { get; set; }
}

我有两个动漫类型的合集。假设他们调用了 a1 和 a2。我想获得 3 个动漫类型的合集:

  1. 仅在a1中的项目
  2. 仅在 a2 中的项目
  3. 两个集合中的项目

我怎样才能做到这一点?我想我应该用 linq 和 Id 值做点什么,但我没能找到合适的解决方案。 顺便说一句,每个动漫系列的Id值都是唯一的(如果两个项目具有相同的Id,则它们代表同一个系列)。

这是一种简单的方法,它具有在 LINQ over objects 和 LINQ over SQL:

中工作的额外好处
var q1=a1.Where(a=>!a2.Any(b=>b.Id==a.Id));
var q2=a2.Where(a=>!a1.Any(b=>b.Id==a.Id));
var a3=a1.Where(a=>a2.Any(b=>b.Id==a.Id));

来自 LINQPAD:

class Anime
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string ImageUrl { get; set; }
}

void Main()
{
    var a1=new Anime[]{new Anime {Id=1,Title="Title1"},new Anime {Id=2,Title="Title2"}};
    var a2=new Anime[]{new Anime {Id=2,Title="Title2"},new Anime {Id=3,Title="Title3"}};

    var q1=a1.Where(a=>!a2.Any(b=>b.Id==a.Id));
    var q2=a2.Where(a=>!a1.Any(b=>b.Id==a.Id));
    var q3=a1.Where(a=>a2.Any(b=>b.Id==a.Id));
    q1.Dump();
    q2.Dump();
    q3.Dump();
}

结果:

您也可以设置自定义 IEqualityComparer 并使用 Intersects,但这通常比在高性能情况下的所有情况都值得,而且在 SQL:[=17 上不适用于 LINQ =]

class Anime
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string ImageUrl { get; set; }
}

class AnimeComparer: IEqualityComparer<Anime>
{
    public bool Equals(Anime a1, Anime a2)
    {
        return (a1.Id==a2.Id);
    }

    public int GetHashCode(Anime a)
    {
        return a.Id.GetHashCode();
    }
}

void Main()
{
    var a1=new Anime[]{new Anime {Id=1,Title="Title1"},new Anime {Id=2,Title="Title2"}};
    var a2=new Anime[]{new Anime {Id=2,Title="Title2"},new Anime {Id=3,Title="Title3"}};

    var ac=new AnimeComparer();
    var q1=a1.Except(a2,ac);
    var q2=a2.Except(a1,ac);
    var q3=a1.Intersect(a2,ac);
    q1.Dump();
    q2.Dump();
    q3.Dump();
}

第三种方法是使用扩展方法。这也不太可能在 SQL 上与 LINQ 一起使用,但不需要修改任何 类,或者如果您有多种对象类型则自定义 IEqualityComparers:

public static class LinqExtensions
{
    public static IEnumerable<TSource> Except<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> comparer)
    {
        return first.Where(x => !second.Any(y => comparer(x, y)));
    }
    public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, Func<TSource, TSource, bool> comparer)
    {
        return first.Where(x => second.Any(y => comparer(x, y)));
    }
}

class Anime
{
   public int Id { get; set; }
   public string Title { get; set; }
   public string ImageUrl { get; set; }
}

void Main()
{
    var a1=new Anime[]{new Anime {Id=1,Title="Title1"},new Anime {Id=2,Title="Title2"}};
    var a2=new Anime[]{new Anime {Id=2,Title="Title2"},new Anime {Id=3,Title="Title3"}};

    var q1=a1.Except(a2,(b1,b2)=>b1.Id==b2.Id);
    var q2=a2.Except(a1,(b1,b2)=>b1.Id==b2.Id);
    var q3=a1.Intersect(a2,(b1,b2)=>b1.Id==b2.Id);
    q1.Dump();
    q2.Dump();
    q3.Dump();
}

第四个选项是使用 morelinq,它结合了无自定义 IEqualityComparers 的简单性和非常高的性能(但仍然不兼容 SQL 上的 LINQ):

class Anime
{
   public int Id { get; set; }
   public string Title { get; set; }
   public string ImageUrl { get; set; }
}

void Main()
{
    var a1=new Anime[]{new Anime {Id=1,Title="Title1"},new Anime {Id=2,Title="Title2"}};
    var a2=new Anime[]{new Anime {Id=2,Title="Title2"},new Anime {Id=3,Title="Title3"}};

    var q1=a1.ExceptBy(a2,k=>k.Id);
    var q2=a2.ExceptBy(a1,k=>k.Id);
    var q3=a1.ExceptBy(q1,k=>k.Id);
    q1.Dump();
    q2.Dump();
    q3.Dump();
}

如果你实现了 GetHashCodeEquals 方法,你可以在 link 的帮助下轻松编写你的代码。

class Anime
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string ImageUrl { get; set; }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        var anime = obj as Anime;
        if (anime == null) return false;

        return this.Id == anime.Id;
    }
}

现在可以做到

var a1 = new List<Anime>()
{
    new Anime() { Id=1, Title="Title1" },
    new Anime() { Id=2, Title="Title2" },
    new Anime() { Id=3, Title="Title3" },
    new Anime() { Id=4, Title="Title4" }
};

var a2 = new List<Anime>()
{
    new Anime() { Id=1, Title="Title1" },
    new Anime() { Id=3, Title="Title3" },
    new Anime() { Id=5, Title="Title5" }
};

var q1 = a1.Except(a2).ToList();
var q2 = a2.Except(a1).ToList();
var q3 = a1.Intersect(a2).ToList();