Как правильно трансформировать коллекции?

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

У меня есть две коллекции типа Аниме. Давайте предположим, что они назвали a1 и a2. Я хочу получить 3 коллекции типа Аниме:

  1. Предметы, которые есть только в а1
  2. Предметы, которые есть только в а2
  3. Предметы, которые есть в обеих коллекциях

Как мне этого добиться? Я думаю, что я должен что-то сделать со значением linq и Id, но мне не удалось найти адекватного решения. Кстати, значение Id уникально для каждой аниме-серии (если два элемента имеют одинаковый Id, они представляют одну и ту же серию).

2 ответа

Решение

Если вы реализуете метод GetHashCode и Equals, вы можете легко написать свой код с помощью ссылки.

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

Это простой способ сделать это, который имеет дополнительное преимущество работы как в LINQ над объектами, так и в LINQ над 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, но это обычно сложнее, чем стоит во всех случаях, кроме ситуаций с высокой производительностью, и не работает с LINQ over SQL:

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

Третий подход заключается в использовании метода расширения. Это также вряд ли будет работать с LINQ поверх SQL, но не требует изменений каких-либо классов или пользовательских 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 с очень высокой производительностью (но все еще не совместим с LINQ поверх SQL):

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();
}
Другие вопросы по тегам