Suppose I have a class like below:
public class A
{
public string prop1 { get; set; }
public List<B> prop2 { get; set; } //B is a user-defined type
// and so on
}
Now suppose I have two objects of type A, a1 and a2. But not all of the properties of them are initialized. I need to check whether a1 has the same values for all the non-null properties of a2 (I don't know what is the term for this, maybe "sub-equality"?).
What I am trying to do is to overwrite Equals() method for all the types used in A, and then iterate through properties of a2 like this, and check the equality of the corresponding properties (if it is a List then I should use HashSet and SetEquals()).
Is there a better (more efficient, less code, etc) way?
There are several techniques, but here is something to start with. I have used an extension method which works all the way down to object (why not), but you could restrict it to higher levels of make it part of your base class itself.
Note that reflection is relatively expensive so if it is the sort of operation that you are doing often, you should consider caching the relevant PropertyInfos.
namespace ConsoleApp1
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
public class BaseClass<T>
{
public string prop1
{
get;
set;
}
public List<T> prop2
{
get;
set;
} //B is a user-defined type
}
public class DescendentClass<T> : BaseClass<T>
{
}
public static class DeepComparer
{
public static bool IsDeeplyEquivalent(this object current, object other)
{
if (current.Equals(other)) return true;
Type currentType = current.GetType();
// Assumption, cannot be equivalent if another class (descendent classes??)
if (currentType != other.GetType())
{
return false;
}
foreach (PropertyInfo propertyInfo in currentType.GetProperties())
{
object currentValue = propertyInfo.GetValue(current, null);
object otherValue = propertyInfo.GetValue(other, null);
// Assumption, nulls for a given property are considered equivalent
if (currentValue == null && otherValue == null)
{
continue;
}
// One is null, the other isn't so are not equal
if (currentValue == null || otherValue == null)
{
return false;
}
ICollection currentCollection = currentValue as ICollection;
if (currentCollection == null)
{
// Not a collection, just check equality
if (!currentValue.Equals(otherValue))
{
return false;
}
}
else
{
// Collection, not interested whether list/array/etc are same object, just that they contain equal elements
// questioner guaranteed that all elements are unique
HashSet<object> elements = new HashSet<object>();
foreach (object o in currentCollection)
{
elements.Add(o);
}
List<object> otherElements = new List<object>();
foreach (object o in (ICollection)otherValue)
{
otherElements.Add(o);
}
// cast below can be safely made because we have already asserted that
// current and other are the same type
if (!elements.SetEquals(otherElements))
{
return false;
}
}
}
return true;
}
}
public class Program
{
public static void Main(string[] args)
{
BaseClass<int> a1 = new BaseClass<int>{prop1 = "Foo", prop2 = new List<int>{1, 2, 3}};
BaseClass<int> a2 = new BaseClass<int>{prop1 = "Foo", prop2 = new List<int>{2, 1, 3}};
BaseClass<int> a3 = new BaseClass<int>{prop1 = "Bar", prop2 = new List<int>{2, 1, 3}};
BaseClass<string> a4 = new BaseClass<string>{prop1 = "Bar", prop2 = new List<string>()};
BaseClass<int> a5 = new BaseClass<int>{prop1 = "Bar", prop2 = new List<int>{1, 3}};
DateTime d1 = DateTime.Today;
DateTime d2 = DateTime.Today;
List<string> s1 = new List<string>{"s1", "s2"};
string[] s2 = {"s1", "s2"};
BaseClass<DayOfWeek> b1 = new BaseClass<DayOfWeek>{prop1 = "Bar", prop2 = new List<DayOfWeek>{DayOfWeek.Monday, DayOfWeek.Tuesday}};
DescendentClass<DayOfWeek> b2 = new DescendentClass<DayOfWeek>{prop1 = "Bar", prop2 = new List<DayOfWeek>{DayOfWeek.Monday, DayOfWeek.Tuesday}};
Console.WriteLine("a1 == a2 : " + a1.IsDeeplyEquivalent(a2)); // true, different order ignored
Console.WriteLine("a1 == a3 : " + a1.IsDeeplyEquivalent(a3)); // false, different prop1
Console.WriteLine("a3 == a4 : " + a3.IsDeeplyEquivalent(a4)); // false, different types
Console.WriteLine("a3 == a5 : " + a3.IsDeeplyEquivalent(a5)); // false, difference in list elements
Console.WriteLine("d1 == d2 : " + d1.IsDeeplyEquivalent(d2)); // true
Console.WriteLine("s1 == s2 : " + s1.IsDeeplyEquivalent(s2)); // false, different types
Console.WriteLine("b1 == b2 : " + s1.IsDeeplyEquivalent(s2)); // false, different types
Console.WriteLine("b1 == b1 : " + b1.IsDeeplyEquivalent(b1)); // true, same object
Console.ReadLine();
}
}
}
Related
As part of a test I am trying to compare similar objects using reflection.
The objects may or may not have multiple levels of nested params.
For example:
public class Connection{
public string Ip{get; set}
public string Id{get; set}
public string Key{get; set}
public string Transport{get; set}
public Parameters ParametersObj = new Parameters();
public class Parameters
{
public string AssignedName { get; set; }
public string CategoryType { get; set; }
public string Status { get; set; }
}
};
This is just an example of a class, I need this method to deal with any type of object without knowing the number of depth level.
I am doing something like this after I have made sure the two objects are of the same type.
bool result = true;
foreach (var objParam in firstObj.GetType().GetProperties())
{
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null || !value1.Equals(value2))
{
logger.Error("Property: " + objParam.Name);
logger.Error("Values: " + value1?.ToString() + " and " + value2?.ToString());
result = false;
}
}
return result;
It works perfectly for the first level of params but it ignores completely any nested objects. In this example I would like it to compare the values inside the parameters object and if they are different the log to print error "Property: Parameters.Status".
I would recommend to look into some tool which already does that (do not know one which does exactly that but FluentAssertions for example can handle object graph comparisons). But in the nutshell you can check if type is primitive or overrides Equals and call your method recursively. Something like the following:
bool Compare(object firstObj, object secondObj)
{
if (object.ReferenceEquals(firstObj, secondObj))
{
return true;
}
var type = firstObj.GetType();
var propertyInfos = firstObj.GetType().GetProperties();
foreach (var objParam in propertyInfos)
{
var methodInfo = objParam.PropertyType.GetMethod(nameof(object.Equals), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, new []{typeof(object)});
var overridesEquals = methodInfo?.GetBaseDefinition().DeclaringType == typeof(object);
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null)
{
// Log
return false;
}
if (object.ReferenceEquals(value1, value2))
{
continue;
}
if (type.IsPrimitive || overridesEquals)
{
if (value1.Equals(value2))
{
continue;
}
// Log?
return false;
}
if (!Compare(value1, value2))
{
// log ?
return false;
}
}
return true;
}
P.S.
Note that Connection.ParametersObj is not a property it is field so it will be ignored by both yours and mine implementations
Consider using source generators instead of reflection.
This does not handle collections.
The problem is that you only do one loop, without looking at the objects within each object. Here's a quick recursive function I threw together. (untested!)
// You can make it non-generic, this just ensures that both arguments are the same type.
static void Go<T>(T left, T right, Action<object, object, PropertyInfo> onFound, int depth = 2)
{
if (left is null)
return;
foreach (var p in left.GetType().GetProperties())
{
var l = p.GetValue(left);
var r = p.GetValue(right);
if (l is null || r is null || !l.Equals(r))
onFound(l, r, p);
if (depth is not 0)
Go(l, r, onFound, depth - 1);
}
}
Usage:
var arg1 = new Connection()
{
ParametersObj = new() { AssignedName = "foo" }
};
var arg2 = new Connection()
{
ParametersObj = new() { AssignedName = "bar" }
};
Go(arg1, arg2, Log); // goes 2 layers deep is you don't specify the last parameter
void Log(object l, object r, PropertyInfo p)
{
logger.Error($"Property: {p.Name}");
logger.Error($"Values: {l} and {r}");
}
I've this class:
public class Pair<T, V>
{
public T A = default;
public V B = default;
public Pair()
{
A = default;
B = default;
}
public Pair(T a, V b)
{
A = a;
B = b;
}
public override bool Equals(object obj)
{
Pair<T, V> other = obj as Pair<T, V>;
return A.Equals(other.A) && B.Equals(other.B);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override string ToString()
{
return "Pair: (" + A.ToString() + " , " + B.ToString() + ")";
}
}
And I have a class with two Pair variables:
public class FakeClass<T>
{
public T LastValue { get; protected set; } = default;
public T CurrentValue = default;
public void Execute()
{
LastValue = CurrentValue
}
}
public class FakeClassWithPair : FakeClass<Pair<int, int>> { }
Now if I execute this code:
FakeClassWithPair fake = new FakeClassWithPair();
fake.CurrentValue.A = 2;
fake.CurrentValue.B = 5;
fake.Execute();
fake.CurrentValue.A = 32;
fake.CurrentValue.B = 53;
In debugging Current Value and Last Value have the same value "32" and "53".
How can I avoid this?
Classes are reference types, so when you set LastValue = CurrentValue, that means both LastValue and CurrentValue refer to the same object.
If you want Value semantics you should declare your Pair as a struct. This means that an assignment does a copy of the value. Except ofc there already are a built in type for this: ValueTuple, with some special syntax that lets you declare types like (int A, int B). There is also a regular Tuple<T1, T2> if you do want a reference type.
Also note that I see no way for your example to run, fake.CurrentValue should be initialized to null and crash when accessed. Using a value type would also solve this, since they cannot be null.
So just change your example to FakeClassWithPair:FakeClass<(int A, int B)> and everything should work as you expect it to.
Definitely do not roll your own class for a pair if you want value semantics. Use the built-in value tuple, defined as (T a, V b).
Also if your content of FakeClass is cloneable then you should take advantage of that (for example arrays are cloneable). So the assignment in Execute() would check if the current value implements ICloneable and proceeds accordingly.
See this example code with output. The first example with fk variable is defined by FakeClass<(int,int)> and the second example with fa variable is defined by FakeClass<int[]>. Some fun code is added to display arrays as list of vales in ToString() in order to mimic the behavior of tuples with arrays.
public class FakeClass<T>
{
public T LastValue { get; protected set; } = default(T);
public T CurrentValue = default(T);
public void Execute()
{
if (CurrentValue is ICloneable cloneable)
{
LastValue = (T)cloneable.Clone();
}
else
{
LastValue = CurrentValue;
}
}
public override string ToString()
{
if (typeof(T).IsArray)
{
object[] last, current;
Array cv = CurrentValue as Array;
if (cv != null)
{
current = new object[cv.Length];
cv.CopyTo(current, 0);
}
else
{
current = new object[0];
}
Array lv = LastValue as Array;
if (lv != null)
{
last = new object[lv.Length];
lv.CopyTo(last, 0);
}
else
{
last = new object[0];
}
return $"Current=[{string.Join(",",current)}], Last=[{string.Join(",",last)}]";
}
return $"Current={CurrentValue}, Last={LastValue}";
}
}
class Program
{
static void Main(string[] args)
{
var fk = new FakeClass<(int a, int b)>();
fk.CurrentValue = (1, 2);
Console.WriteLine(fk);
// Current=(1, 2), Last=(0, 0)
fk.Execute();
fk.CurrentValue = (3, 4);
Console.WriteLine(fk);
// Current=(3, 4), Last=(1, 2)
var fa = new FakeClass<int[]>();
fa.CurrentValue = new int[] { 1, 2 };
Console.WriteLine(fa);
//Current=[1,2], Last=[]
fa.Execute();
fa.CurrentValue = new int[] { 3, 4 };
Console.WriteLine(fa);
//Current=[3,4], Last=[1,2]
}
}
I have two Lists:
List<L1>, List<L2>
L1 = { detailId = 5, fileName = "string 1" }{ detailId = 5, fileName = "string 2" }
L2 = { detailId = 5, fileName = "string 2" }{ detailId = 5, fileName = "string 3" }
That I want to combine them with no duplicates:
List<L3>
L1 = { detailId = 5, fileName = "string 1" }{ detailId = 5, fileName = "string 2" }{ detailId = 5, fileName = "string 3" }
I've tried:
L1.Union(L2).ToList();
L1.Concat(L2).Distinct().ToList();
But both return with duplicates (1, 2, 2, 3).
Not sure what I'm missing.
edit Here's the method. It takes one list and creates another from a delimited string, and tries to combine them.
private List<Files> CombineList(int detailId, string fileNames)
{
List<Files> f1 = new List<Files>();
List<Files> f2 = new List<Files>();
f1 = GetFiles(detailId, false);
if (f1[0].fileNames != "")
{
string[] names = fileNames.Split('|');
for (int i = 0; i < names.Length; i++)
{
Files x = new Files();
x.detailId = detailId;
x.fileNames = names[i];
f2.Add(x);
}
List<Files> f3 = f1.Union(f2).ToList();
}
return f3;
}
From MSDN, for Union :
The default equality comparer, Default, is used to compare values of
the types that implement the IEqualityComparer(Of T) generic
interface. To compare a custom data type, you need to implement this
interface and provide your own GetHashCode and Equals methods for the
type.
link
Since you use a custom type, you need to either override the GetHashCode and Equals or provide an object that implements the IEqualityComparer interface (preferably IEquatable) and provide it as a second parameter to Union.
Here's a simple example of implementing such a class.
I don't like overriding the Files class equals object and getHashCode since you are interfering with the object. Let another object do that and just pass it in. This way if you have an issue with it later on, just swap it out and pass another IEqualityComparer
Here's an example you can just test out
public void MainMethod()
{
List<Files> f1 = new List<Files>() { new Files() { detailId = 5, fileName = "string 1" }, new Files() { detailId = 5, fileName = "string 2" } };
List<Files> f2 = new List<Files>() { new Files() { detailId = 5, fileName = "string 2" }, new Files() { detailId = 5, fileName = "string 3" } };
var f3 = f1.Union(f2, new FilesComparer()).ToList();
foreach (var f in f3)
{
Console.WriteLine(f.detailId + " " + f.fileName);
}
}
public class Files
{
public int detailId;
public string fileName;
}
public class FilesComparer : IEqualityComparer<Files>
{
public bool Equals(Files x, Files y)
{
return x.fileName == y.fileName;
}
public int GetHashCode(Files obj)
{
return obj.fileName.GetHashCode();
}
}
If your elements do not implement some kind of comparison interface (Object.Equals, IEquatable, IComparable, etc.) then any equality test between them will involve ReferenceEquals, in which two different objects are two different objects, even if all their members contain identical values.
If you're merging a list of objects, you will need to define some criteria for the equality comparisons. The example below demonstrates this:
class MyModelTheUniqueIDComparer : IEqualityComparer<MyModel>
{
public bool Equals(MyModel x, MyModel y)
{
return x.SomeValue == y.SomeValue && x.OtherValue == y.OtherValue;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(MyModel myModel)
{
unchecked
{
int hash = 17;
hash = hash * 31 + myModel.SomeValue.GetHashCode();
hash = hash * 31 + myModel.OtherValue.GetHashCode();
return hash;
}
}
}
Then you can call to get the result:
var result = q1.Union(q2, new MyModelTheUniqueIDComparer());
From MSDN Enumerable.Union Method:
If you want to compare sequences of objects of a custom data type, you
have to implement the IEqualityComparer < T > generic interface in the
class.
A sample implementation specific to your Files class so that the Union works correctly when merging two collections of custom type:
public class Files : IEquatable<Files>
{
public string fileName { get; set; }
public int detailId { get; set; }
public bool Equals(Files other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return detailId.Equals(other.detailId) && fileName.Equals(other.fileName);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the fileName field if it is not null.
int hashFileName = fileName == null ? 0 : fileName.GetHashCode();
//Get hash code for the detailId field.
int hashDetailId = detailId.GetHashCode();
//Calculate the hash code for the Files object.
return hashFileName ^ hashDetailId;
}
}
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;
}
I'm looking to take an in-memory object (or JSON serialization of an object) and emit C# code to produce an equivalent object.
This would be useful for pulling known-good examples from a repository to use as starting points in unit tests. We have considered deserializing JSON, but C# code would have an edge when it comes to refactoring.
There is an interesting Visual Studio extension that addresses this; the Object Exporter. It allows serialization of an in-memory object into C# Object initialization code, JSON and XML. I haven't tried it yet but looks intriguing; will update after trying it out.
If your model is simple, you could use reflection and a string builder to output C# directly. I've done this to populate unit test data exactly as you discussed.
The code sample below was written in a few minutes and generated an object initializer that needed some hand tweaking. A more robust / less buggy function could be written if you plan on doing this a lot.
The second function is recursive, iterating over any Lists within the object, and generating code for those as well.
Disclaimer: This worked for my simple model with basic data types. It generated code that needed cleanup but allowed me to move on quickly. It is only here to serve as an example of how this could be done. Hopefully, it inspires someone to write their own.
In my case, I had an instance of this large dataset (results) that was loaded from the database. In order to remove the database dependency from my unit test, I handed the object to this function which spits out the code that allowed me to mock the object in my test class.
private void WriteInstanciationCodeFromObject(IList results)
{
//declare the object that will eventually house C# initialization code for this class
var testMockObject = new System.Text.StringBuilder();
//start building code for this object
ConstructAndFillProperties(testMockObject, results);
var codeOutput = testMockObject.ToString();
}
private void ConstructAndFillProperties(StringBuilder testMockObject, IList results)
{
testMockObject.AppendLine("var testMock = new " + results.GetType().ToString() + "();");
foreach (object obj in results)
{
//if this object is a list, write code for its contents
if (obj.GetType().GetInterfaces().Contains(typeof(IList)))
{
ConstructAndFillProperties(testMockObject, (IList)obj);
}
testMockObject.AppendLine("testMock.Add(new " + obj.GetType().Name + "() {");
foreach (var property in obj.GetType().GetProperties())
{
//if this property is a list, write code for its contents
if (property.PropertyType.GetInterfaces().Contains(typeof(IList)))
{
ConstructAndFillProperties(testMockObject, (IList)property.GetValue(obj, null));
}
testMockObject.AppendLine(property.Name + " = (" + property.PropertyType + ")\"" + property.GetValue(obj, null) + "\",");
}
testMockObject.AppendLine("});");
}
}
It's possible the object will have a TypeConverter that supports conversion to InstanceDescriptor, which is what the WinForms designer uses when emitting C# code to generate an object. If it can't convert to an InstanceDescriptor, it will attempt to use a parameterless constructor and simply set public properties. The InstanceDescriptor mechanism is handy, since it allows you to specify various construction options such as constructors with parameters or even static factory method calls.
I have some utility code I've written that emits loading of an in-memory object using IL, which basically follows the above pattern (use InstanceDescriptor if possible and, if not, simply write public properties.) Note that this will only produce an equivalent object if the InstanceDescriptor is properly implemented or setting public properties is sufficient to restore object state. If you're emitting IL, you can also cheat and read/write field values directly (this is what the DataContractSerializer supports), but there are a lot of nasty corner cases to consider.
I'm a novice at this as well, but I also needed to take a C# object that defined a hierarchy and extract it to an object initializer to ease setting up a unit test. I borrowed heavily from the above and wound up with this. I'd like to improve the way it handles recognizing user classes.
http://github.com/jefflomax/csharp-object-to-object-literal/blob/master/Program.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ObjectInitializer
{
public class Program
{
public enum Color { Red, Green, Blue, Yellow, Fidget } ;
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
}
public class Thing
{
public int ThingId { get; set; }
public string ThingName { get; set; }
public List<Foo> Foos { get; set; }
}
public class Widget
{
public long Sort { get; set; }
public char FirstLetter { get; set; }
}
public class TestMe
{
public Color Color { get; set; }
public long Key { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public DateTime? NCreated { get; set; }
public bool Deleted { get; set; }
public bool? NDeleted { get; set; }
public double Amount { get; set; }
public Thing MyThing { get; set; }
public List<Thing> Things { get; set; }
public List<Widget> Widgets { get; set; }
}
static void Main(string[] args)
{
var testMe = new TestMe
{
Color = Program.Color.Blue,
Key = 3,
Name = "SAK",
Created = new DateTime(2013,10,20,8,0,0),
NCreated = (DateTime?)null,
Deleted = false,
NDeleted = null,
Amount = 13.1313,
MyThing = new Thing(){ThingId=1,ThingName="Thing 1"},
Things = new List<Thing>
{
new Thing
{
ThingId=4,
ThingName="Thing 4",
Foos = new List<Foo>
{
new Foo{FooId=1, FooName="Foo 1"},
new Foo{FooId=2,FooName="Foo2"}
}
},
new Thing
{
ThingId=5,
ThingName="Thing 5",
Foos = new List<Foo>()
}
},
Widgets = new List<Widget>()
};
var objectInitializer = ToObjectInitializer(testMe);
Console.WriteLine(objectInitializer);
// This is the returned C# Object Initializer
var x = new TestMe { Color = Program.Color.Blue, Key = 3, Name = "SAK", Created = new DateTime(2013, 10, 20, 8, 0, 0), NCreated = null, Deleted = false, NDeleted = null, Amount = 13.1313, MyThing = new Thing { ThingId = 1, ThingName = "Thing 1", Foos = new List<Foo>() }, Things = new List<Thing> { new Thing { ThingId = 4, ThingName = "Thing 4", Foos = new List<Foo> { new Foo { FooId = 1, FooName = "Foo 1" }, new Foo { FooId = 2, FooName = "Foo2" } } }, new Thing { ThingId = 5, ThingName = "Thing 5", Foos = new List<Foo>() } }, Widgets = new List<Widget>() };
Console.WriteLine("");
}
public static string ToObjectInitializer(Object obj)
{
var sb = new StringBuilder(1024);
sb.Append("var x = ");
sb = WalkObject(obj, sb);
sb.Append(";");
return sb.ToString();
}
private static StringBuilder WalkObject(Object obj, StringBuilder sb)
{
var properties = obj.GetType().GetProperties();
var type = obj.GetType();
var typeName = type.Name;
sb.Append("new " + type.Name + " {");
bool appendComma = false;
DateTime workDt;
foreach (var property in properties)
{
if (appendComma) sb.Append(", ");
appendComma = true;
var pt = property.PropertyType;
var name = pt.Name;
var isList = property.PropertyType.GetInterfaces().Contains(typeof(IList));
var isClass = property.PropertyType.IsClass;
if (isList)
{
IList list = (IList)property.GetValue(obj, null);
var listTypeName = property.PropertyType.GetGenericArguments()[0].Name;
if (list != null && list.Count > 0)
{
sb.Append(property.Name + " = new List<" + listTypeName + ">{");
sb = WalkList( list, sb );
sb.Append("}");
}
else
{
sb.Append(property.Name + " = new List<" + listTypeName + ">()");
}
}
else if (property.PropertyType.IsEnum)
{
sb.AppendFormat("{0} = {1}", property.Name, property.GetValue(obj));
}
else
{
var value = property.GetValue(obj);
var isNullable = pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>);
if (isNullable)
{
name = pt.GetGenericArguments()[0].Name;
if (property.GetValue(obj) == null)
{
sb.AppendFormat("{0} = null", property.Name);
continue;
}
}
switch (name)
{
case "Int64":
case "Int32":
case "Int16":
case "Double":
case "Float":
sb.AppendFormat("{0} = {1}", property.Name, value);
break;
case "Boolean":
sb.AppendFormat("{0} = {1}", property.Name, Convert.ToBoolean(value) == true ? "true" : "false");
break;
case "DateTime":
workDt = Convert.ToDateTime(value);
sb.AppendFormat("{0} = new DateTime({1},{2},{3},{4},{5},{6})", property.Name, workDt.Year, workDt.Month, workDt.Day, workDt.Hour, workDt.Minute, workDt.Second);
break;
case "String":
sb.AppendFormat("{0} = \"{1}\"", property.Name, value);
break;
default:
// Handles all user classes, should likely have a better way
// to detect user class
sb.AppendFormat("{0} = ", property.Name);
WalkObject(property.GetValue(obj), sb);
break;
}
}
}
sb.Append("}");
return sb;
}
private static StringBuilder WalkList(IList list, StringBuilder sb)
{
bool appendComma = false;
foreach (object obj in list)
{
if (appendComma) sb.Append(", ");
appendComma = true;
WalkObject(obj, sb);
}
return sb;
}
}
}
I stumbled across this while looking for the same kind of method Matthew described, and was inspired by Evan's answer to write my own extension method. It generates compilable C# code as a string that can be copy/pasted into Visual Studio. I didn't bother with any particular formatting and just output the code on one line and use ReSharper to format it nicely. I've used it with some big DTOs that we were passing around and so far it works like a charm.
Here's the extension method and a couple helper methods:
public static string ToCreationMethod(this object o)
{
return String.Format("var newObject = {0};", o.CreateObject());
}
private static StringBuilder CreateObject(this object o)
{
var builder = new StringBuilder();
builder.AppendFormat("new {0} {{ ", o.GetClassName());
foreach (var property in o.GetType().GetProperties())
{
var value = property.GetValue(o);
if (value != null)
{
builder.AppendFormat("{0} = {1}, ", property.Name, value.GetCSharpString());
}
}
builder.Append("}");
return builder;
}
private static string GetClassName(this object o)
{
var type = o.GetType();
if (type.IsGenericType)
{
var arg = type.GetGenericArguments().First().Name;
return type.Name.Replace("`1", string.Format("<{0}>", arg));
}
return type.Name;
}
The method GetCSharpString contains the logic, and it's open to extension for any particular type. It was enough for me that it handled strings, ints, decimals, dates anything that implements IEnumerable:
private static string GetCSharpString(this object o)
{
if (o is String)
{
return string.Format("\"{0}\"", o);
}
if (o is Int32)
{
return string.Format("{0}", o);
}
if (o is Decimal)
{
return string.Format("{0}m", o);
}
if (o is DateTime)
{
return string.Format("DateTime.Parse(\"{0}\")", o);
}
if (o is IEnumerable)
{
return String.Format("new {0} {{ {1}}}", o.GetClassName(), ((IEnumerable)o).GetItems());
}
return string.Format("{0}", o.CreateObject());
}
private static string GetItems(this IEnumerable items)
{
return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + String.Format("{0}, ", item.GetCSharpString()));
}
I hope someone finds this useful!
It may comes a bit late, but here is my 5cents on that problem.
The mentioned Visual Studio Extension (OmarElabd/ObjectExporter) was a good idea, but I needed to generate C# code from in-memory objects at runtime, during unit test execution. This is what evolved from the original problem: https://www.nuget.org/packages/ObjectDumper.NET/
ObjectDumper.Dump(obj, DumpStyle.CSharp); returns C# initializer code from a variable. Please let me know if you find issues, you might want to report them on github.
There's a solution similar to what Evan proposed, but a bit better suited for my particular task.
After playing a bit with CodeDOM and Reflection it turned out that it would be too complicated in my case.
The object was serialized as XML, so the natural solution was to use XSLT to simply transform it to the object creation expression.
Sure, it covers only certain types of the cases, but maybe will work for someone else.
Here is an update to #revlucio's solution that adds support for booleans and enums.
public static class ObjectInitializationSerializer
{
private static string GetCSharpString(object o)
{
if (o is bool)
{
return $"{o.ToString().ToLower()}";
}
if (o is string)
{
return $"\"{o}\"";
}
if (o is int)
{
return $"{o}";
}
if (o is decimal)
{
return $"{o}m";
}
if (o is DateTime)
{
return $"DateTime.Parse(\"{o}\")";
}
if (o is Enum)
{
return $"{o.GetType().FullName}.{o}";
}
if (o is IEnumerable)
{
return $"new {GetClassName(o)} \r\n{{\r\n{GetItems((IEnumerable)o)}}}";
}
return CreateObject(o).ToString();
}
private static string GetItems(IEnumerable items)
{
return items.Cast<object>().Aggregate(string.Empty, (current, item) => current + $"{GetCSharpString(item)},\r\n");
}
private static StringBuilder CreateObject(object o)
{
var builder = new StringBuilder();
builder.Append($"new {GetClassName(o)} \r\n{{\r\n");
foreach (var property in o.GetType().GetProperties())
{
var value = property.GetValue(o);
if (value != null)
{
builder.Append($"{property.Name} = {GetCSharpString(value)},\r\n");
}
}
builder.Append("}");
return builder;
}
private static string GetClassName(object o)
{
var type = o.GetType();
if (type.IsGenericType)
{
var arg = type.GetGenericArguments().First().Name;
return type.Name.Replace("`1", $"<{arg}>");
}
return type.Name;
}
public static string Serialize(object o)
{
return $"var newObject = {CreateObject(o)};";
}
}