Issues in Xunit.Assert.Collection - C# - c#

I have a Class Library, it contains the following Model and Method
Model:
public class Employee {
public int EmpId { get; set; }
public string Name { get; set; }
}
Method:
public class EmployeeService {
public List<Employee> GetEmployee() {
return new List<Employee>() {
new Employee() { EmpId = 1, Name = "John" },
new Employee() { EmpId = 2, Name = "Albert John" },
new Employee() { EmpId = 3, Name = "Emma" },
}.Where(m => m.Name.Contains("John")).ToList();
}
}
I have a Test Method
[TestMethod()]
public void GetEmployeeTest() {
EmployeeService obj = new EmployeeService();
var result = obj.GetEmployee();
Xunit.Assert.Collection<Employee>(result, m => Xunit.Assert.Contains("John",m.Name));
}
I got an Exception message
Assert.Collection() Failure
Collection: [Employee { EmpId = 1, Name = "John" }, Employee { EmpId = 2, Name = "Albert John" }]
Expected item count: 1
Actual item count: 2
My requirement is to check all the items.Name should contain the sub string "John". Kindly assist me how to check using Xunit.Assert.Collection

It appears that Assert.Collection only uses each element inspector once. So, for your test, the following works:
If the sequence result has exactly two elements:
[Fact]
public void GetEmployeeTest()
{
EmployeeService obj = new EmployeeService();
var result = obj.GetEmployee();
Assert.Collection(result, item => Assert.Contains("John", item.Name),
item => Assert.Contains("John", item.Name));
}
If there are many elements, changing the Assert to
Assert.All(result, item => Assert.Contains("John", item.Name));
should give you the result you are expecting.

This is an expansion on Ayb4btu's answer for those who aren't interested in the order of items in the collection.
The following method is based on the original XUnit implementation, and will allow you to test using a very similar interface:
public static class TestExpect
{
public static void CollectionContainsOnlyExpectedElements<T>(IEnumerable<T> collectionToTest, params Func<T, bool>[] inspectors)
{
int expectedLength = inspectors.Length;
T[] actual = collectionToTest.ToArray();
int actualLength = actual.Length;
if (actualLength != expectedLength)
throw new CollectionException(collectionToTest, expectedLength, actualLength);
List<Func<T, bool>> allInspectors = new List<Func<T, bool>>(inspectors);
int index = -1;
foreach (T elementToTest in actual)
{
try
{
index++;
Func<T, bool> elementInspectorToRemove = null;
foreach (Func<T, bool> elementInspector in allInspectors)
{
if (elementInspector.Invoke(elementToTest))
{
elementInspectorToRemove = elementInspector;
break;
}
}
if (elementInspectorToRemove != null)
allInspectors.Remove(elementInspectorToRemove);
else
throw new CollectionException(collectionToTest, expectedLength, actualLength, index);
}
catch (Exception ex)
{
throw new CollectionException(collectionToTest, expectedLength, actualLength, index, ex);
}
}
}
}
The difference here is that for a collection
string[] collectionToTest = { "Bob", "Kate" };
Both the following lines will not produce a CollectionException
TestExpect.CollectionContainsOnlyExpectedElements(collectionToTest, x => x.Equals("Bob"), x => x.Equals("Kate"));
TestExpect.CollectionContainsOnlyExpectedElements(collectionToTest, x => x.Equals("Kate"), x => x.Equals("Bob"));
Whereas using Assert.Collection - Only the first of the above two lines will work as the collection of inspectors is evaluated in order.
There is a potential performance impact using this method, but if you are only testing fairly small sized collections (as you probably will be in a unit test), you will never notice the difference.

Use Assert.Contains(result, item => item.Name == "John");

Related

C# linkedlist find method throws error "cannot convert lambda expression to type 'Item' because it is not a delegate type"

using System;
using System.Collections.Generic;
class Program
{
static void Main(string[] args)
{
LinkedList<Item> list = new LinkedList<Item>();
// Add some items to the list
list.AddLast(new Item { Id = 1, Name = "Item 1" });
list.AddLast(new Item { Id = 2, Name = "Item 2" });
list.AddLast(new Item { Id = 3, Name = "Item 3" });
// Find an item by its id
int idToFind = 2;
LinkedListNode<Item> node = list.Find((Item x) => x.Id == idToFind);
if (node != null)
{
Console.WriteLine("Found item: {0}", node.Value.Name);
}
else
{
Console.WriteLine("Item not found");
}
}
}
class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
I am searching for the reason why lambda is not allowed in Find method but same is working in FirstOrDefault?
Find doesn't expect a lambda but an Item: https://learn.microsoft.com/dotnet/api/system.collections.generic.linkedlist-1.find?view=net-7.0.
So you'd need this:
LinkedListNode<Item> node = list.Find(myItem);
which is pretty unhandy when you want to search for an Item that satisfies a specific condition. That's where FirstOrDefault comes into play:
LinkedListNode<Item> node = list.FirstOrDefault(x => x.id == idToFind);
I think the problem is in others List type instance you can use Find(Predicate<T> match) to get the item which you want. For example
var list = new List<string>().Find(x => x == "a");
This is because List<T> implement the method
public class List<T>
{
public T? Find(Predicate<T> match)
}
But now which you used is LinkedListNode<T> the Find() implemention
in it is
public class LinkedList<T>
{
public LinkedListNode<T>? Find(T value)
}
So that's why you got a complile error.

Search Generic List<T> for Id property

I have a Generic List which can contain different class objects. Al these classes have the same base class which has a Id property.
I would like to do a search for the Id in the List and extract the first occourence, something like this
T result = myGenericList.Where(i => i.Id == id).FirstOrDefault()
However I cannot get to the properties, I only get as far as: i => i.
EDIT:
I'm asked to provide the code for the myGenericList. Here it goes:
public static IEnumerable<T> Initialize()
{
List<T> allCalls = new List<T>();
var phoneCalls = new PhoneCall[]
{
new PhoneCall{Id = 1, Date= DateTime.Now.AddDays(-1), DurationSec = 60, Phone = "", Region = new Country { From = "", To = ""}},
...
};
foreach (PhoneCall s in phoneCalls)
{
allCalls.Add(s as T);
}
var dataCalls = new DataCall[]
{
new DataCall{Id = 6, WebData = 5120, Phone = "", Region = new Country { From = "", To = ""}},
...
};
foreach (DataCall s in dataCalls)
{
allCalls.Add(s as T);
}
var smsCalls = new SmsCall[]
{
new SmsCall{Id = 6, SmsData = 512, Phone = "", Region = new Country { From = "", To = ""}},
...
};
foreach (SmsCall s in smsCalls)
{
allCalls.Add(s as T);
}
return allCalls;
Regarding the T that is a class which I have defined at the top of my Repository.cs class
public class Repository<T> : IRepository<T> where T : class
What you should do is define what T is and not cast the items to T. As the compiler has no way to know what T is it does not know it has the Id property. Use a generic constraint to do so:
public static IEnumerable<T> Initialize<T where T : BaseClass>()
{
var allCalls = new List<T>();
allCalls.Add(new PhoneCall { /* Details */};
return allCalls;
}
After question update - move the constraint from being one of the function to being one of the class
Also you can use the FirstOrDefault overload that gets a predicate:
var result = myGenericList.FirstOrDefault(i => i.Id == id);
Have you tried casting to the base class?
myGenericList
.Cast<BaseClass>()
.FirstOrDedault(i => i.Id === id)
Or alternatively
myGenericList.FirstOrDefault(i => (i as BaseClass).Id === id)

Lambda expression as inline data in xUnit

I'm pretty new to xUnit and here's what I'd like to achieve:
[Theory]
[InlineData((Config y) => y.Param1)]
[InlineData((Config y) => y.Param2)]
public void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
Basically, I have a GenericConfig object which contains Config and other kind of configurations, but I need to check that every single parameter is valid. Since they're all string, I wanted to simplify using [InlineData] attribute instead of writing N equals tests.
Unfortunately the error I'm getting is "Cannot convert lambda expression to type 'object[]' because it's not a delegate type", which is pretty much clear.
Do you have any idea on how to overcome this?
In addition to the already posted answers. The test cases can be simplified by directly yielding the lambdas.
public class ConfigTestDataProvider
{
public static IEnumerable<object[]> TestCases
{
get
{
yield return new object [] { (Func<Config, object>)((x) => x.Param1) };
yield return new object [] { (Func<Config, object>)((x) => x.Param2) };
}
}
}
This test ConfigTestDataProvider can then directly inject the lambdas.
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(Func<Config, object> func)
{
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
Actually, I was able to find a solution which is a bit better than the one provided by Iqon (thank you!).
Apparently, the InlineData attribute only supports primitive data types. If you need more complex types, you can use the MemberData attribute to feed the unit test with data from a custom data provider.
Here's how I solved the problem:
public class ConfigTestCase
{
public static readonly IReadOnlyDictionary<string, Func<Config, string>> testCases = new Dictionary<string, Func<Config, string>>
{
{ nameof(Config.Param1), (Config x) => x.Param1 },
{ nameof(Config.Param2), (Config x) => x.Param2 }
}
.ToImmutableDictionary();
public static IEnumerable<object[]> TestCases
{
get
{
var items = new List<object[]>();
foreach (var item in testCases)
items.Add(new object[] { item.Key });
return items;
}
}
}
And here's the test method:
[Theory]
[MemberData(nameof(ConfigTestCase.TestCases), MemberType = typeof(ConfigTestCase))]
public void Test(string currentField)
{
var func = ConfigTestCase.testCases.FirstOrDefault(x => x.Key == currentField).Value;
var config = serviceProvider.GetService<GenericConfig>();
var result = func(config.Config1);
Assert.True(!string.IsNullOrEmpty(result));
}
I could maybe come up with something a bit better or cleaner, but for now it works and the code is not duplicated.
I have the problem the same to you, and I found the solution that using TheoryData class and MemberData attribute. Here is the example and I hope the code usefully:
public class FooServiceTest
{
private IFooService _fooService;
private Mock<IFooRepository> _fooRepository;
//dummy data expression
//first parameter is expression
//second parameter is expected
public static TheoryData<Expression<Func<Foo, bool>>, object> dataExpression = new TheoryData<Expression<Func<Foo, bool>>, object>()
{
{ (p) => p.FooName == "Helios", "Helios" },
{ (p) => p.FooDescription == "Helios" && p.FooId == 1, "Helios" },
{ (p) => p.FooId == 2, "Poseidon" },
};
//dummy data source
public static List<Foo> DataTest = new List<Foo>
{
new Foo() { FooId = 1, FooName = "Helios", FooDescription = "Helios Description" },
new Foo() { FooId = 2, FooName = "Poseidon", FooDescription = "Poseidon Description" },
};
//constructor
public FooServiceTest()
{
this._fooRepository = new Mock<IFooRepository>();
this._fooService = new FooService(this._fooRepository.Object);
}
[Theory]
[MemberData(nameof(dataExpression))]
public void Find_Test(Expression<Func<Foo, bool>> expression, object expected)
{
this._fooRepository.Setup(setup => setup.FindAsync(It.IsAny<Expression<Func<Foo, bool>>>()))
.ReturnsAsync(DataTest.Where(expression.Compile()));
var actual = this._fooService.FindAsync(expression).Result;
Assert.Equal(expected, actual.FooName);
}
}
Oddly delegates are not objects, but Actions or Funcs are. To do this, you have to cast the lambda to one of these types.
object o = (Func<Config, string>)((Config y) => y.Param1)
But doing this, your expression is not constant anymore. So this will prevent usage in an Attribute.
There is no way of passing lambdas as attributes.
One possible solution would be to use function calls, instead of attributes. Not as pretty, but could solve your problem without duplicate code:
private void HasConfiguration(Func<Config, string> item)
{
var configuration = serviceProvider.GetService<GenericConfig>();
var x = item(configuration.Config1); // Config1 is of type Config
Assert.True(!string.IsNullOrEmpty(x));
}
[Theory]
public Test1()
{
HasConfiguration((Config y) => y.Param1);
}
[Theory]
public Test2()
{
HasConfiguration((Config y) => y.Param2);
}
public class HrcpDbTests
{
[Theory]
[MemberData(nameof(TestData))]
public void Test(Expression<Func<bool>> exp)
{
// Arrange
// Act
// Assert
}
public static IEnumerable<object[]> TestData
{
get
{
Expression<Func<bool>> mockExp1 = () => 1 == 0;
Expression<Func<bool>> mockExp2 = () => 1 != 2;
return new List<object[]>
{
new object[]
{
mockExp1
},
new object[]
{
mockExp2
}
}
}
}
}

C# Calculate Combinations from Multiple Lists of Custom Objects with No Repetition

So I have been struggling to find a way to create all combinations with no repetition from multiple lists containing custom objects. There are some additional constraints that make it a bit more challenging, of course.
Basically I am parsing a bunch of data from a .csv file that contains part information. This data is then passed on to a custom object and then those objects are added to lists based on their "group." (See code below)
So once the information has been parsed I now have 6 lists containing any number of elements. Now I need to generate all combinations between those 6 lists following these rules:
One object from groupA
Two objects from groupB (no repetition)
Three objects from groupC (no repetition)
One object from groupD
One object from groupE
One object from groupF
These objects are then used to create a ModuleFull object, and my overall end result should be a List<ModuleFull> containing all the combinations generated from the part lists.
I was able to figure out a way to do this using LINQ although I did not test it using lists of custom objects because I realized my lists all contain different numbers of elements.
So any help that I could get in coming up with a method to solve this using recursion would be greatly appreciated.
Here is the code parsing the data:
using (TextFieldParser parser = new TextFieldParser(#"c:\temp\test.csv"))
{
parser.TextFieldType = FieldType.Delimited;
parser.SetDelimiters(",");
while (!parser.EndOfData)
{
string[] fields = parser.ReadFields();
Part tempPart = new Part(fields[0], fields[2], fields[1], double.parse(fields[4]), long.parse(fields[3]));
allParts.Add(tempPart);
if (tempPart.group == "A")
{
aParts.Add(tempPart);
}
else if (tempPart.group == "B")
{
bParts.Add(tempPart);
}
else if (tempPart.group == "C")
{
cParts.Add(tempPart);
}
else if (tempPart.group == "D")
{
dParts.Add(tempPart);
}
else if (tempPart.group == "E")
{
eParts.Add(tempPart);
}
else if (tempPart.group == "F")
{
fParts.Add(tempPart);
}
}
Below are the two classes for the objects that fill the lists:
public class Part
{
public string idNum; //0 locations when being parsed
public string name; //2
public string group; //1
public double tolerance; //4
public long cost; //3
public Part(string id, string nm, string grp, double tol, long cst)
{
idNum = id;
name = nm;
group = grp;
tolerance = tol;
cost = cst;
}
}
public class ModuleFull
{
public Part groupA;
public Part groupBOne;
public Part groupBTwo;
public Part groupCOne;
public Part groupCTwo;
public Part groupCThree;
public Part groupD;
public Part groupE;
public Part groupF;
public ModuleFull(Part a, Part b1, Part b2, Part c1, Part c2, Part c3, Part d, Part e, Part f)
{
groupA = a;
groupBOne = b1;
groupBTwo = b2;
groupCOne = c1;
groupCTwo = c2;
groupCThree = c3;
groupD = d;
groupE = e;
groupF = f;
}
}
The code below uses a custom enumerator to get unique combinations. Very clean solution.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace IEnumerable_IEnumerator_Recursive
{
class Program
{
const string FILENAME = #"c:\temp\test.csv";
static void Main(string[] args)
{
Parser parser = new Parser(FILENAME);
int level = 0;
List<Part> root = new List<Part>();
Part.Recurse(level, root);
}
}
public class Parser
{
public Boolean EndOfData = false;
public Parser(string filename)
{
StreamReader reader = new StreamReader(filename);
string inputLine = "";
while ((inputLine = reader.ReadLine()) != null)
{
inputLine = inputLine.Trim();
if (inputLine.Length > 0)
{
string[] fields = inputLine.Split(new char[] { ',' });
Part tempPart = new Part(fields[0], fields[1], fields[2], fields[3], fields[4]);
Part.allParts.Add(tempPart);
}
}
Part.MakeDictionary();
}
}
public class PartEnumerator : IEnumerator<List<Part>>
{
List<Part> parts = null;
public static SortedDictionary<string, int> maxCount = new SortedDictionary<string, int>() {
{"A", 1},
{"B", 2},
{"C", 3},
{"D", 1},
{"E", 1},
{"F", 1}
};
public int size = 0;
List<int> enumerators = null;
public PartEnumerator(string name, List<Part> parts)
{
this.parts = parts;
size = maxCount[name];
enumerators = new List<int>(new int[size]);
Reset();
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public List<Part> Current
{
get
{
try
{
List<Part> returnParts = new List<Part>();
foreach (int enumerator in enumerators)
{
returnParts.Add(parts[enumerator]);
}
return returnParts;
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
public void Reset()
{
for (int count = 0; count < enumerators.Count; count++)
{
enumerators[count] = count;
}
}
public Boolean MoveNext()
{
Boolean moved = true;
int listSize = parts.Count;
int enumNumbers = enumerators.Count;
//only use enumerators up to the size of list
if (listSize < enumNumbers)
{
enumNumbers = listSize;
}
Boolean ripple = true;
int enumCounter = enumNumbers;
if (enumCounter > 0)
{
while ((ripple == true) && (--enumCounter >= 0))
{
ripple = false;
int maxCount = listSize - (enumNumbers - enumCounter);
if (enumerators[enumCounter] >= maxCount)
{
ripple = true;
}
else
{
for (int i = enumCounter; i < enumNumbers; i++)
{
if (i == enumCounter)
{
enumerators[i] += 1;
}
else
{
enumerators[i] = enumerators[i - 1] + 1;
}
}
}
}
if ((enumCounter <= 0) && (ripple == true))
{
moved = false;
}
}
return moved;
}
public void Dispose()
{
}
}
public class Part
{
public static List<Part> allParts = new List<Part>();
public static Dictionary<string, PartEnumerator> partDict = new Dictionary<string, PartEnumerator>();
public string idNum; //0 locations when being parsed
public string name; //2
public string group; //1
public double tolerance; //4
public long cost; //3
public Part()
{
}
public Part(string id, string nm, string grp, string tol, string cst)
{
idNum = id;
name = nm;
group = grp;
tolerance = double.Parse(tol);
cost = long.Parse(cst);
}
public static void MakeDictionary()
{
var listPartEnum = Part.allParts.GroupBy(x => x.name)
.Select(x => new { Key = x.Key, List = new PartEnumerator(x.Key, x.ToList()) });
foreach (var partEnum in listPartEnum)
{
partDict.Add(partEnum.Key, partEnum.List);
}
}
public static string[] NAMES = { "A", "B", "C", "D", "E", "F" };
public static void Recurse(int level, List<Part> results)
{
Boolean moved = true;
if (level < PartEnumerator.maxCount.Keys.Count)
{
//level is equivalent to names in the Part Enumerator dictionary A to F
string name = NAMES[level];
PartEnumerator enumerator = partDict[name];
enumerator.Reset();
while ((enumerator != null) && moved)
{
List<Part> allParts = new List<Part>(results);
allParts.AddRange((List<Part>)enumerator.Current);
int currentLevel = level + 1;
Recurse(currentLevel, allParts);
moved = enumerator.MoveNext();
}
}
else
{
string message = string.Join(",", results.Select(x => string.Format("[id:{0},name:{1}]", x.name, x.idNum)).ToArray());
Console.WriteLine(message);
}
}
}
}
I used following input file
1,A,X,0,0
2,A,X,0,0
3,A,X,0,0
4,A,X,0,0
5,A,X,0,0
1,B,X,0,0
2,B,X,0,0
3,B,X,0,0
4,B,X,0,0
5,B,X,0,0
1,C,X,0,0
2,C,X,0,0
3,C,X,0,0
4,C,X,0,0
5,C,X,0,0
1,D,X,0,0
2,D,X,0,0
3,D,X,0,0
4,D,X,0,0
5,D,X,0,0
1,E,X,0,0
2,E,X,0,0
3,E,X,0,0
4,E,X,0,0
5,E,X,0,0
1,F,X,0,0
2,F,X,0,0
3,F,X,0,0
4,F,X,0,0
5,F,X,0,0
This can be solved with two related methods. One is a method to generate all combinations of items from a list. This deals with your cases where you want more than one from a set, like group B and C. The other is a method to get you all the ways to combine one element from each list, which is otherwise known as a Cartesian product and is, in a way, a special case of the first method.
I recently wrote a library of combinatorial functions that includes both of these, so I can share my implementation with you. My library is on Github if you want to look at the source code, and can be installed from NuGet if you'd like. (The examples below are slightly simplified to fit your situation; in my fuller versions, the combinations method has different modes that allow you to specify whether it matters what order the output items are in, and whether it's allowed to use source items more than once. Neither are needed here, so they've been omitted.)
So, the first of those methods looks something like this:
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> source, int combinationSize)
{
if (combinationSize > source.Count())
{
return new List<IEnumerable<T>>();
}
if (source.Count() == 1)
{
return new[] { source };
}
var indexedSource = source
.Select((x, i) => new
{
Item = x,
Index = i
})
.ToList();
return indexedSource
.SelectMany(x => indexedSource
.OrderBy(y => x.Index != y.Index)
.Skip(1)
.OrderBy(y => y.Index)
.Skip(x.Index)
.Combinations(combinationSize - 1)
.Select(y => new[] { x }.Concat(y).Select(z => z.Item))
);
}
The second method is from a blog post by Eric Lippert (which was actually inspired by another StackOverflow question), and looks like this:
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
if (sequences == null)
{
throw new ArgumentNullException(nameof(sequences));
}
IEnumerable<IEnumerable<T>> emptyProduct = new IEnumerable<T>[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item }))
.Where(x => x.Any());
}
The two methods can be combined like this:
var groupA = new[] { "a", "aa", "aaa", "aaaa", "aaaaa" };
var groupB = new[] { "b", "bb", "bbb", "bbbb", "bbbbb" };
var groupC = new[] { "c", "cc", "ccc", "cccc", "ccccc" };
var groupD = new[] { "d", "dd", "ddd", "dddd", "ddddd" };
var groupE = new[] { "e", "ee", "eee", "eeee", "eeeee" };
var groupF = new[] { "f", "ff", "fff", "ffff", "fffff" };
var options = new[]
{
groupA.Combinations(1), // One object from groupA
groupB.Combinations(2), // Two objects from groupB (no repetition)
groupC.Combinations(3), // Three objects from groupC (no repetition)
groupD.Combinations(1), // One object from groupD
groupE.Combinations(1), // One object from groupE
groupF.Combinations(1) // One object from groupF
};
return options.CartesianProduct();
So, we generate the various ways of satisfying each of your sub-conditions first: one from this group, two from that group, etc. Then, we look at all ways of putting those together to form a group of subgroups. The result is an IEnumerable<IEnumerable<T>> where T is the type of what you started with - in this case, string, but for you it could be something else. You can then iterate over this and use each set to build your result type.
Be aware that, like many combinatorial problems, this can scale quite fast. For example, with my test data this returns 62,500 possible combinations.

Compare Properties automatically

I want to get the names of all properties that changed for matching objects. I have these (simplified) classes:
public enum PersonType { Student, Professor, Employee }
class Person {
public string Name { get; set; }
public PersonType Type { get; set; }
}
class Student : Person {
public string MatriculationNumber { get; set; }
}
class Subject {
public string Name { get; set; }
public int WeeklyHours { get; set; }
}
class Professor : Person {
public List<Subject> Subjects { get; set; }
}
Now I want to get the objects where the Property values differ:
List<Person> oldPersonList = ...
List<Person> newPersonList = ...
List<Difference> = GetDifferences(oldPersonList, newPersonList);
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) {
//how to check the properties without casting and checking
//for each type and individual property??
//can this be done with Reflection even in Lists??
}
In the end I would like to have a list of Differences like this:
class Difference {
public List<string> ChangedProperties { get; set; }
public Person NewPerson { get; set; }
public Person OldPerson { get; set; }
}
The ChangedProperties should contain the name of the changed properties.
I've spent quite a while trying to write a faster reflection-based solution using typed delegates. But eventually I gave up and switched to Marc Gravell's Fast-Member library to achieve higher performance than with normal reflection.
Code:
internal class PropertyComparer
{
public static IEnumerable<Difference<T>> GetDifferences<T>(PropertyComparer pc,
IEnumerable<T> oldPersons,
IEnumerable<T> newPersons)
where T : Person
{
Dictionary<string, T> newPersonMap = newPersons.ToDictionary(p => p.Name, p => p);
foreach (T op in oldPersons)
{
// match items from the two lists by the 'Name' property
if (newPersonMap.ContainsKey(op.Name))
{
T np = newPersonMap[op.Name];
Difference<T> diff = pc.SearchDifferences(op, np);
if (diff != null)
{
yield return diff;
}
}
}
}
private Difference<T> SearchDifferences<T>(T obj1, T obj2)
{
CacheObject(obj1);
CacheObject(obj2);
return SimpleSearch(obj1, obj2);
}
private Difference<T> SimpleSearch<T>(T obj1, T obj2)
{
Difference<T> diff = new Difference<T>
{
ChangedProperties = new List<string>(),
OldPerson = obj1,
NewPerson = obj2
};
ObjectAccessor obj1Getter = ObjectAccessor.Create(obj1);
ObjectAccessor obj2Getter = ObjectAccessor.Create(obj2);
var propertyList = _propertyCache[obj1.GetType()];
// find the common properties if types differ
if (obj1.GetType() != obj2.GetType())
{
propertyList = propertyList.Intersect(_propertyCache[obj2.GetType()]).ToList();
}
foreach (string propName in propertyList)
{
// fetch the property value via the ObjectAccessor
if (!obj1Getter[propName].Equals(obj2Getter[propName]))
{
diff.ChangedProperties.Add(propName);
}
}
return diff.ChangedProperties.Count > 0 ? diff : null;
}
// cache for the expensive reflections calls
private Dictionary<Type, List<string>> _propertyCache = new Dictionary<Type, List<string>>();
private void CacheObject<T>(T obj)
{
if (!_propertyCache.ContainsKey(obj.GetType()))
{
_propertyCache[obj.GetType()] = new List<string>();
_propertyCache[obj.GetType()].AddRange(obj.GetType().GetProperties().Select(pi => pi.Name));
}
}
}
Usage:
PropertyComparer pc = new PropertyComparer();
var diffs = PropertyComparer.GetDifferences(pc, oldPersonList, newPersonList).ToList();
Performance:
My very biased measurements showed that this approach is about 4-6 times faster than the Json-Conversion and about 9 times faster than ordinary reflections. But in fairness, you could probably speed up the other solutions quite a bit.
Limitations:
At the moment my solution doesn't recurse over nested lists, for example it doesn't compare individual Subject items - it only detects that the subjects lists are different, but not what or where. However, it shouldn't be too hard to add this feature when you need it. The most difficult part would probably be to decide how to represent these differences in the Difference class.
We start with 2 simple methods:
public bool AreEqual(object leftValue, object rightValue)
{
var left = JsonConvert.SerializeObject(leftValue);
var right = JsonConvert.SerializeObject(rightValue);
return left == right;
}
public Difference<T> GetDifference<T>(T newItem, T oldItem)
{
var properties = typeof(T).GetProperties();
var propertyValues = properties
.Select(p => new {
p.Name,
LeftValue = p.GetValue(newItem),
RightValue = p.GetValue(oldItem)
});
var differences = propertyValues
.Where(p => !AreEqual(p.LeftValue, p.RightValue))
.Select(p => p.Name)
.ToList();
return new Difference<T>
{
ChangedProperties = differences,
NewItem = newItem,
OldItem = oldItem
};
}
AreEqual just compares the serialized versions of two objects using Json.Net, this keeps it from treating reference types and value types differently.
GetDifference checks the properties on the passed in objects and compares them individually.
To get a list of differences:
var oldPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bob" }
};
var newPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bobby" }
};
var diffList = oldPersonList.Zip(newPersonList, GetDifference)
.Where(d => d.ChangedProperties.Any())
.ToList();
Everyone always tries to get fancy and write these overly generic ways of extracting data. There is a cost to that.
Why not be old school simple.
Have a GetDifferences member function Person.
virtual List<String> GetDifferences(Person otherPerson){
var diffs = new List<string>();
if(this.X != otherPerson.X) diffs.add("X");
....
}
In inherited classes. Override and add their specific properties. AddRange the base function.
KISS - Keep it simple. It would take you 10 minutes of monkey work to write it, and you know it will be efficient and work.
I am doing it by using this:
//This structure represents the comparison of one member of an object to the corresponding member of another object.
public struct MemberComparison
{
public static PropertyInfo NullProperty = null; //used for ROOT properties - i dont know their name only that they are changed
public readonly MemberInfo Member; //Which member this Comparison compares
public readonly object Value1, Value2;//The values of each object's respective member
public MemberComparison(PropertyInfo member, object value1, object value2)
{
Member = member;
Value1 = value1;
Value2 = value2;
}
public override string ToString()
{
return Member.name+ ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
}
}
//This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
public static List<MemberComparison> ReflectiveCompare<T>(T x, T y)
{
List<MemberComparison> list = new List<MemberComparison>();//The list to be returned
if (x.GetType().IsArray)
{
Array xArray = x as Array;
Array yArray = y as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(MemberComparison.NullProperty, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else
{
foreach (PropertyInfo m in x.GetType().GetProperties())
//Only look at fields and properties.
//This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
if (!m.PropertyType.IsArray && (m.PropertyType == typeof(String) || m.PropertyType == typeof(double) || m.PropertyType == typeof(int) || m.PropertyType == typeof(uint) || m.PropertyType == typeof(float)))
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if (!object.Equals(yValue, xValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
list.Add(new MemberComparison(m, yValue, xValue));
}
else if (m.PropertyType.IsArray)
{
Array xArray = m.GetValue(x, null) as Array;
Array yArray = m.GetValue(y, null) as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(m, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else if (m.PropertyType.IsClass)
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if ((xValue == null || yValue == null) && !(yValue == null && xValue == null))
list.Add(new MemberComparison(m, xValue, yValue));
else if (!(xValue == null || yValue == null))
{
var compare = ReflectiveCompare(m.GetValue(x, null), m.GetValue(y, null));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
return list;
}
Here you have a code which does what you want with Reflection.
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP)
{
List<Difference> allDiffs = new List<Difference>();
foreach (Person oldPerson in oldP)
{
foreach (Person newPerson in newP)
{
Difference curDiff = GetDifferencesTwoPersons(oldPerson, newPerson);
allDiffs.Add(curDiff);
}
}
return allDiffs;
}
private Difference GetDifferencesTwoPersons(Person OldPerson, Person NewPerson)
{
MemberInfo[] members = typeof(Person).GetMembers();
Difference returnDiff = new Difference();
returnDiff.NewPerson = NewPerson;
returnDiff.OldPerson = OldPerson;
returnDiff.ChangedProperties = new List<string>();
foreach (MemberInfo member in members)
{
if (member.MemberType == MemberTypes.Property)
{
if (typeof(Person).GetProperty(member.Name).GetValue(NewPerson, null).ToString() != typeof(Person).GetProperty(member.Name).GetValue(OldPerson, null).ToString())
{
returnDiff.ChangedProperties.Add(member.Name);
}
}
}
return returnDiff;
}

Categories