I need a generic class to compare two list instances. I have the code below, but it is quite messy and inefficient. How can I make it better and definitely faster?
CallByName is an extension that uses reflection to get the property.
public class ListObjectComparator
{
#region PROPRIETES
public List<object> NewList {get; private set;}
public List<object> RemovedList { get; private set; }
public List<object> CommunList { get; private set; }
#endregion
#region METHODES PUBLICS
public bool Run<T>(string iCommunTextPropertyName, List<T> iOriginalList, List<T> iNewList)
{
return Run(iCommunTextPropertyName, iOriginalList, iCommunTextPropertyName, iNewList);
}
public bool Run<T>(string iOriginalPropertyName, List<T> iOriginalList,string iNewPropertyName, List<T> iNewList)
{
if (iOriginalPropertyName.IsNotNull() &&
iNewPropertyName.IsNotNull() &&
iOriginalList != null &&
iNewList != null)
{
NewList = new List<object>();
RemovedList = new List<object>();
CommunList = new List<object>();
foreach (object originalItem in iOriginalList)
{
object research = iNewList.Where(a => a.CallByName(iNewPropertyName).ToString() == originalItem.CallByName(iOriginalPropertyName).ToString()).FirstOrDefault();
if (research == null)
{
RemovedList.Add(originalItem);
}
else
{
CommunList.Add(research);
}
}
foreach (object newItem in iNewList)
{
object research = iOriginalList.Where(a => a.CallByName(iOriginalPropertyName).ToString() == newItem.CallByName(iNewPropertyName).ToString()).FirstOrDefault();
if (research == null) { NewList.Add(newItem); };
}
return true;
}
return false;
}
}
Answer:
public class ListComparator<TOriginal,TNew>
{
public ListComparator(IEnumerable<TOriginal> iOriginalList, Func<TOriginal, IComparable> iOriginalProperty, IEnumerable<TNew> iNewList, Func<TNew, IComparable> iNewProperty)
{
if (iOriginalProperty == null ||
iNewProperty == null) { throw new ArgumentNullException(); };
if (iOriginalList.IsNullOrEmpty() )
{
NewList = (iNewList != null) ? iNewList.ToList() : null;
return;
}
if (iNewList.IsNullOrEmpty())
{
RemovedList = (iOriginalList != null) ? iOriginalList.ToList() : null;
return;
}
NewList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item == null
select tnew).ToList();
CommunList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item != null
select tnew).ToList();
CommunPairList = (from tnew in iNewList.ToList()
join toriginal in iOriginalList.ToList()
on iNewProperty(tnew)
equals iOriginalProperty(toriginal) into gj
from item in gj.DefaultIfEmpty()
where item != null
select new KeyValuePair<TOriginal, TNew>(item, tnew)).ToList();
RemovedList = (from toriginal in iOriginalList.ToList()
join tnew in iNewList.ToList()
on iOriginalProperty(toriginal)
equals iNewProperty(tnew) into gj
from item in gj.DefaultIfEmpty()
where item == null
select toriginal).ToList();
return;
}
#region PROPRIETES
public List<TNew> NewList { get; private set; }
public List<TOriginal> RemovedList { get; private set; }
public List<TNew> CommunList { get; private set; }
/// <summary>
/// Obtient la liste de pair avec l'original en key et le nouveau en value
/// </summary>
public List<KeyValuePair<TOriginal,TNew>> CommunPairList { get; private set; }
#endregion
}
use:
List<Tuple<string, string>> list1 = new List<Tuple<string, string>>();
List<Tuple<string, string>> list2 = new List<Tuple<string, string>>();
list1.Add(new Tuple<string, string>("AA", "zefzef"));
list1.Add(new Tuple<string, string>("A1", "iulyu"));
list2.Add(new Tuple<string, string>("Abb", "szefez"));
list2.Add(new Tuple<string, string>("A1", "zevzez"));
ListComparator<Tuple<string, string>, Tuple<string, string>> comp = new ListComparator<Tuple<string, string>, Tuple<string, string>>(list1, x => x.Item1, list2, a => a.Item1);
OUTPUT :
1 commun, 1 removed, 1 new
thanks
Lösung
To get the list of items that the two lists share in common, look at Enumerable.Intersect. That is:
var same = OriginalList.Intersect(NewList, comparer);
If you want to know which items in NewList aren't in OriginalList (i.e. the list of added items), you'd use Enumerable.Except:
var added = NewList.Except(OriginalList, comparer);
If you want to know which items were deleted, you use Except again, but switch the order:
var deleted = OriginalList.Except(NewList, comparer);
If you're comparing different properties, then probably your best bet is to create intermediate lists that contain the properties and references to the objects. For example: (There might be typos in the code, but this shows the general idea.)
class TempObj: IEquatable<TempObj>
{
public string Key { get; set; }
public object Item { get; set; }
public bool Equals(TempObj other)
{
return Key.Equals(other.Key);
}
public override int GetHashCode()
{
return Key.GetHashCode();
}
}
var listOrig = OriginalList.Select(x => new TempObj(
x..CallByName(iOriginalPropertyName).ToString());
var listNew = NewList.Select(x => new TempObj(
x.CallByName(iNewPropertyName).ToString());
Now you can do Intersect or Except on listNew and listOrig, which will compare the property names. You can then pull the Item values from the resulting list.
Andere Tipps
Your class actually isn't generic here (although it should be), only your methods are.
Try this:
public class ListObjectComparator<T>
{
#region Public Methods
public static IEnumerable<T> Run(IEnumerable<T> originalItems, IEnumerable<T> newItems, Func<T,T,bool> comparer)
{
foreach(var originalItem in originalItems)
{
bool found = false;
foreach (var newItem in newItems)
{
if (comparer(originalItem,newItem))
{
found = true;
itemToRemove = newItem;
break;
}
}
if (found)
{
newItems.Remove(itemToRemove);
yield return originalItem;
}
}
}
#endregion
}
Run compares originalItems with newItems and returns a list of items that are the same using the ComparePredicate you supply.