Как правильно трансформировать коллекции?
class Anime
{
public int Id { get; set; }
public string Title { get; set; }
public string ImageUrl { get; set; }
}
У меня есть две коллекции типа Аниме. Давайте предположим, что они назвали a1 и a2. Я хочу получить 3 коллекции типа Аниме:
- Предметы, которые есть только в а1
- Предметы, которые есть только в а2
- Предметы, которые есть в обеих коллекциях
Как мне этого добиться? Я думаю, что я должен что-то сделать со значением 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();
}