Comparing (specific) object properties in c# - c#

Building upon this answer for comparing objects in C#
Comparing object properties in c#
Knowing this is a complex topic, I wanted to handle a few more specific structures.
While this code will match properties if they are simple value types such as this object:
public class BasicStuff
{
public int anInt { get; set; }
public bool aBool { get; set; }
}
But as soon as it gets any more complex, this code fails.
So what I would like to do is make it a bit more usable for nested objects of the above, such as:
public class NestedStuff
{
public BasicStuff theBasic { get; set; }
}
Any array of the above, such as:
public class ArrayStuff
{
public BasicStuff[] theBasicArray { get; set; }
}
And finally any combination of the above:
public class AllTheStuff
{
public int anInt { get; set; }
public bool aBool { get; set; }
public BasicStuff theBasic { get; set; }
public BasicStuff[] theBasicArray { get; set; }
}
So what I came up with was the following:
public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
{
if (AObj != null && BObj != null)
{
Type type = typeof(T);
List<string> ignoreList = new List<string>(ignore);
foreach (PropertyInfo pInfo in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!ignoreList.Contains(pInfo.Name))
{
if (pInfo.PropertyType.IsArray)
{
object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
string t = AValue.GetType().ToString();
if (!AllPublicPropertiesEqual<object>(AValue, BValue))
return false;
}
else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string))
//else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
{
object AValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (!AllPublicPropertiesEqual<object>(BValue, AValue))
return false;
}
else
{
object selfValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object toValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)))
return false;
}
}
}
return true;
}
return AObj == BObj;
}
Only this fails because when recursively calling AllPublicPropertiesEqual, I need to pass the specific values type rather than just a generic object type.
But I dont know how to do this.... Any ideas are greatly appreciated...

Your method does not have to be generic. You can change the beginning of the method to:
public static bool AllPublicPropertiesEqual(object AObj, object BObj, params string[] ignore)
{
if (AObj != null && BObj != null)
{
Type type = AObj.GetType();
if (BObj.GetType() != type)
throw new Exception("Objects should be of the same type");
....
}
....
}

Well first thing what is that you should create Interface for those classes because from what I see you can combine those classes but all of them will have the same properties. Second option is create some Base abstract class with those properties and inherit from it. It is up to you what you choose.
Than create in this Base class or Interface Equals functions where you will equal directly the shared properties it is much easier and I think more efficient :-)
public abstract class BaseStaff {
public int anInt { get; set; }
public bool aBool { get; set; }
public abstract bool Equals(BaseStaff anotherStaff);
}
Then you can use this class and create your BasicStaff class and AllTheStaff class too in BasicStaff implement Equals method like this.
public override bool Equals(BaseStaff staff) {
this.anInt == staff.anInt &&
this.aBool == staff.aBool;
}
In AllTheStaff u can than override this method like this
public override bool Equals(BaseStaff staff) {
bool baseEquals = base.Equals(staff);
bool basicStaffEquals = this.BasicStaff.Equals(staff);
return baseEquals || basicStaffEquals;
}
This is just core idea and maybe I dont understand you well what you really want to achieve but hope it helps you :)

tested Solution
public static bool AllPublicPropertiesEqual<T>(T AObj, T BObj, params string[] ignore) where T : class
{
if (AObj != null && BObj != null)
{
Type type = typeof(T);
List<string> ignoreList = new List<string>(ignore);
foreach (PropertyInfo pInfo in type.GetCType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!ignoreList.Contains(pInfo.Name))
{
if (pInfo.PropertyType.IsArray)
{
object aElementValues = (type.GetProperty(pInfo.Name).GetValue(AObj, null));
object bElementValues = (type.GetProperty(pInfo.Name).GetValue(BObj, null));
if (aElementValues != null && bElementValues != null)
{
List<object> AListValues = new List<object>();
List<object> BListValues = new List<object>();
foreach (var v in (IEnumerable)aElementValues)
AListValues.Add(v);
foreach (var v in (IEnumerable)bElementValues)
BListValues.Add(v);
if (AListValues.Count == BListValues.Count)
{
object[] aArray = AListValues.ToArray();
object[] bArray = BListValues.ToArray();
for (int i = 0; i < aArray.Length; i++)
{
if (!AllPublicPropertiesEqual(aArray[i], bArray[i]))
return false;
}
}
else
return false;
}
else if ((aElementValues == null) != (bElementValues == null))
return false;
}
else if (!pInfo.PropertyType.IsValueType && !pInfo.PropertyType.IsPrimitive && !pInfo.PropertyType.IsEnum && pInfo.PropertyType != typeof(string))
//else if (Convert.GetTypeCode(pInfo.PropertyType) == TypeCode.Object)
{
object AObjectValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object BObjectValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (!AllPublicPropertiesEqual(BObjectValue, AObjectValue))
return false;
}
else
{
object aValue = type.GetProperty(pInfo.Name).GetValue(AObj, null);
object bValue = type.GetProperty(pInfo.Name).GetValue(BObj, null);
if (aValue != bValue && (aValue == null || !aValue.Equals(bValue)))
return false;
}
}
}
return true;
}
return AObj == BObj;
}
}

Related

.Net reflection to get nested property with custom attribute

I want to know the best way to get the prop info and value using reflection for a nested class by its custom attribute name.
With below code I can get the prop info via recursion. But is there a better way or using LINQ. Note that I do not want to hard code the class type as similar to other solution
I also want to get the property value by custom attribute
e.g var propValue = ?????
public class PlanetRoot
{
public void GetNeighborMoon()
{
Planet planet = new Planet();
Product product = new Product();
Neighbor neighbor = new Neighbor();
neighbor.Moons = 10;
neighbor.RingColor = "Red";
product.Neighbors = new List<Neighbor>();
product.Neighbors.Add(neighbor);
planet.Product = product;
//1. Get the RingColor property info of neighbor with attribute MyDBField(Name = "NeighborRing") . Is there a better way
PropertyInfo propInfo = null;
DoRecursiveGetProperty(planet.GetType(), "NeighborRing", out propInfo );
//2. Get the RingColor property value of neighbor with attribute MyDBField(Name = "NeighborRing")
//var propValue = GetPropertyValue(????);
}
}
private static PropertyInfo DoRecursiveGetProperty(Type type, string attribName, out PropertyInfo propInfo)
{
PropertyInfo[] pi = type.GetProperties();
propInfo= null;
foreach (PropertyInfo p in pi)
{
var dbFieldAttribute = (MyDBFieldAttribute)Attribute.GetCustomAttribute(p, typeof(MyDBFieldAttribute));
if (dbFieldAttribute != null && attribName.ToUpper() == dbFieldAttribute.Name.ToUpper())
{
propInfo= p;
//Console.WriteLine(p.Name + " : " + (dbFieldAttribute != null && dbFieldAttribute.Name != null ? dbFieldAttribute.Name : "****"));
return true;
}
if (p.PropertyType.IsClass && !p.PropertyType.IsValueType && !p.PropertyType.IsPrimitive
&& p.PropertyType.FullName != "System.String")
if (propInfo != null) return true;
else DoRecursiveGetProperty(p.PropertyType, attribName, out propInfo);
}
return false;
}
public class Planet
{
public string PlanetId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Product Product { get; set; }
[MyDBField(Name="PubDate")]
public string Publishdate { get; set; }
}
public class Product
{
public string ProductId { get; set; }
public List<Neighbor> Neighbors { get; set; }
}
public class Neighbor
{
[MyDBField(Name = "NeighborRing")]
public string RingColor { get; set; }
public int Moons { get; set; }
}
public class MyDBFieldAttribute : System.Attribute
{
public string Name { get; set; }
}
In order to get the value for a nested member that might be in a collection, you need to iterate the collections, and track the current object.
Assuming you don't need the resulting PropInfo for other reasons, just try to get the value:
private static bool TryRecursiveGetValueWithMyDBFieldName(object startObject, string attribName, out object propValue) {
PropertyInfo[] pi = startObject.GetType().GetProperties();
foreach (PropertyInfo p in pi) {
var dbFieldAttribute = (MyDBFieldAttribute)Attribute.GetCustomAttribute(p, typeof(MyDBFieldAttribute));
if (dbFieldAttribute != null && dbFieldAttribute.Name.Equals(attribName, StringComparison.CurrentCultureIgnoreCase)) {
//Console.WriteLine(p.Name + " : " + (dbFieldAttribute != null && dbFieldAttribute.Name != null ? dbFieldAttribute.Name : "****"));
propValue = p.GetValue(startObject);
return true;
}
if (p.PropertyType.IsClass && !p.PropertyType.IsValueType && !p.PropertyType.IsPrimitive &&
!p.PropertyType.FullName.StartsWith("System.")) {
var tryObject = p.GetValue(startObject);
if (tryObject != null && TryRecursiveGetValueWithMyDBFieldName(tryObject, attribName, out propValue))
return true;
}
if (p.PropertyType.IsClass && p.GetValue(startObject) is IEnumerable ip) {
foreach (var obj in ip) {
if (obj != null && TryRecursiveGetValueWithMyDBFieldName(obj, attribName, out propValue))
return true;
}
}
}
propValue = default;
return false;
}
To use it, call with the initial object:
var foundAttrib = TryRecursiveGetValueWithMyDBFieldName(planet, "NeighborRing", out var propValue);
NOTE: This will return the value of the first object with a matching attribute, as e.g. every member of the List<Neighbor> member will have the MyDBField attribute with the Name property of NeighborRing.

Converting Flat file to List which is a property of another class

public class DummyResponse
{
public int UpdatedRecords { get; set; }
public string Id { get; set; }
public bool Isvalid { get; set; }
}
public class Request
{
public List<DummyResponse> Changes { get; set; }
public string ReuestedBy { get; set; }
public Request()
{
Changes = new List<DummyResponse>();
}
}
I have a flat file which contains tab separated data for Dummy Response.
I want this to be serialized to Request object.
The implementation needs should be generic as in I only need user to pass T (Request) in this case and identify the correct sub type to be filled from flat file.
I have below Code to convert it into Object. How ever its only working for properties having string type.
interface ICollectionBuilder
{
object Build(IList dictionaries);
}
internal class CollectionBuilder<T> : ICollectionBuilder where T : new()
{
public object Build(IList dictionaries)
{
var dictConverter = new DictionaryConerter<T>();
return dictionaries
.OfType<IDictionary<string, object>>()
.Select(dict => dictConverter.ConvertTyped(dict))
.ToList();
}
}
interface IDictionaryConverter
{
object Convert(IDictionary<string, object> dict);
}
internal class DictionaryConerter<T> : IDictionaryConverter where T : new()
{
public object Convert(IDictionary<string, object> dict)
{
return ConvertTyped(dict);
}
public T ConvertTyped(IDictionary<string, object> dict)
{
T t = new T();
var properties = t.GetType().GetProperties();
foreach (KeyValuePair<string, object> curr in dict)
{
if (String.IsNullOrEmpty(curr.Key)) continue;
if (curr.Value == null) continue;
Type valType = null;
Type newType = null;
PropertyInfo currProperty = null;
foreach (PropertyInfo p in properties)
{
if (String.IsNullOrEmpty(p.Name)) continue;
if (String.Compare(p.Name.ToLower(), curr.Key.ToLower()) == 0)
{
valType = t.GetType().GetProperty(p.Name).PropertyType;
newType = Nullable.GetUnderlyingType(valType) ?? valType;
currProperty = p;
break;
}
}
object newVal = curr.Value;
var curDict = curr.Value as IDictionary<string, object>;
var curList = curr.Value as IList;
if (curDict != null && newType.GetConstructor(Type.EmptyTypes) != null)
{
newVal = ((IDictionaryConverter)Activator.CreateInstance(typeof(DictionaryConerter<>).MakeGenericType(newType))).Convert(curDict);
}
else if (
curList != null &&
curList.OfType<IDictionary<string, object>>().Any() &&
newType.IsGenericType &&
newType.GetGenericTypeDefinition() == typeof(List<>) &&
newType.GetGenericArguments()[0].GetConstructor(Type.EmptyTypes) != null)
{
newVal = ((ICollectionBuilder)Activator.CreateInstance(typeof(CollectionBuilder<>).MakeGenericType(newType.GetGenericArguments()[0]))).Build(curList);
}
t.GetType().GetProperty(currProperty.Name).SetValue(t, newVal);
}
return t;
}
}
Example usage:
void Main ()
{
var dict = new Dictionary<string,object>();
dict.Add("ReuestedBy",abc);
var innerDict = new Dictionary<string,object>();
var list = new LIst<Dictionary<string,object>>();
innerDict.Add("UpdatedRecords","45");
innerDict.Add("Id","1");
innerDict.Add("IsValid","False");
dict.Add("Changes",list )
}
The problem here is it not working for any other type other than string.
I fixed it using below code while setting value
propertyVal = Convert.ChangeType(propertyVal, targetType);
propertyInfo.SetValue(inputObject, propertyVal, null);

How to iterate through nested properties of an object

I am trying to loop through all properties in an object including nested objects and objects in collections, to check if the property is of DateTime data type. If it is, convert the value to UTC time leaving everything intact including the structure and the value of other properties untouched.
The structure of my classes as follows:
public class Custom1 {
public int Id { get; set; }
public DateTime? ExpiryDate { get; set; }
public string Remark { get; set; }
public Custom2 obj2 { get; set; }
}
public class Custom2 {
public int Id { get; set; }
public string Name { get; set; }
public Custom3 obj3 { get; set; }
}
public class Custom3 {
public int Id { get; set; }
public DateTime DOB { get; set; }
public IEnumerable<Custom1> obj1Collection { get; set; }
}
public static void Main() {
Custom1 obj1 = GetCopyFromRepository<Custom1>();
// this only loops through the properties of Custom1 but doesn't go into properties in Custom2 and Custom3
var myType = typeof(Custom1);
foreach (var property in myType.GetProperties()) {
// ...
}
}
How do I loop through the properties in obj1 and traverse further down obj2 then obj3 then obj1Collection? The function have to be generic enough because the Type passed to the function cannot be determined at design/compile time. Conditional statements to test for Types should be avoided since they might be class Custom100
//avoid conditional statements like this
if (obj is Custom1) {
//do something
} else if (obj is Custom2) {
//do something else
} else if (obj is Custom3) {
//do something else
} else if ......
This is not a complete answer but I would start from here.
var myType = typeof(Custom1);
ReadPropertiesRecursive(myType);
private static void ReadPropertiesRecursive(Type type)
{
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?))
{
Console.WriteLine("test");
}
if (property.PropertyType.IsClass)
{
ReadPropertiesRecursive(property.PropertyType);
}
}
}
I think it's far from the original question but ReadPropertiesRecursive above fall in infinite loop on
class Chain { public Chain Next { get; set; } }
I'd rather propose version with accumulation
private static void ReadPropertiesRecursive(Type type, IEnumerable<Type> visited)
{
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == typeof(DateTime) || property.PropertyType == typeof(DateTime?))
{
Console.WriteLine("test");
}
else if (property.PropertyType.IsClass && !visited.Contains(property.PropertyType))
{
ReadPropertiesRecursive(property.PropertyType, visited.Union(new Type[] { property.PropertyType }));
}
}
}
Check myType and if IsClass is true, then traverse into it. You'll probably want to make this recursive.
public static class ReadPropertiesExtension
{
public static void ReadPropertiesRecursive<T>(object value, Func<T, T> func)
{
if (value is null)
return;
if (value is IEnumerable)
{
IList collection = (IList)value;
foreach (var val in collection)
ReadPropertiesRecursive(val, func);
return;
}
var type = value.GetType();
foreach (PropertyInfo property in type.GetProperties())
{
if (property.PropertyType == type)
continue;
if (!property.CanRead)
continue;
var val = property.GetValue(value);
if (property.CanWrite && val is T param)
{
property.SetValue(value, func(param));
}
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
ReadPropertiesRecursive(val, func);
}
}
}
}
Here is a recursive function which also supports Enumerables.
It takes a parameter func and executes this method anticipating that func takes parameter T and returns same param.
You can call it like- ReadPropertiesExtension.ReadPropertiesRecursive<double>(value, func1);
Note: Currently, it doesn't support objects which contain dictionary in it.

C# Generic Objects Compareing

This is generic class to compare two objects with a user defined property, but it doesn't work correctly.
public class ObjectComparer<T> : IEqualityComparer<T>
{
private string _propertyName;
private string PropertyName
{
get { return _propertyName; }
set
{
_propertyName = value;
PropertyInfo = typeof(T).GetProperty(PropertyName);
}
}
public ObjectComparer(string propertyName)
{
PropertyName = propertyName;
}
private PropertyInfo PropertyInfo { get; set; }
public int GetHashCode(T type)
{
if (type== null)
{
return 0;
}
return PropertyInfo.GetHashCode();
}
public bool Equals(T obj1, T obj2)
{
if (ReferenceEquals(obj1, obj2))
{
return true;
}
if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null))
{
return false;
}
return PropertyInfo.GetValue(obj1, null) == PropertyInfo.GetValue(obj2, null);
}
}
Issue 1)type == null (Possible compare of value type with null)
Issue 2)Use ReferenceEquals in this position is correct?
Update
"Word" class:
public class Word
{
public string EnglishText { get; set; }
public string LocalText { get; set; }
public string EditedText { get; set; }
}
Usage :
var except = Program.Localizer.DefaultLanguage.Words.Except(CurrentSelectedLanguage.Words, new ObjectComparer<Word>("EnglishText"));
Number of different objects based on the "EnglishName" is not correct.
Class authentic and Modified below.
Special thanks to Sriram Sakthivel
public class ObjectComparer<T> : IEqualityComparer<T>
{
private string _propertyName;
private string PropertyName
{
get { return _propertyName; }
set
{
_propertyName = value;
PropertyInfo = typeof(T).GetProperty(PropertyName);
}
}
public ObjectComparer(string propertyName)
{
PropertyName = propertyName;
}
private PropertyInfo PropertyInfo { get; set; }
public int GetHashCode(T obj)
{
if (ReferenceEquals(obj, null))
{
return 0;
}
return PropertyInfo.GetHashCode();
}
public bool Equals(T obj1, T obj2)
{
if (ReferenceEquals(obj1, obj2))
{
return true;
}
if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null))
{
return false;
}
return Equals(PropertyInfo.GetValue(obj1, null),
PropertyInfo.GetValue(obj2, null));
}
}
Your GetHashCode is wrong. You're calling PropertyInfo.GetHashCode It should be using the Property value instead.
public int GetHashCode(T obj)
{
if (object.ReferenceEquals(obj, null))
{
return 0;
}
var value = PropertyInfo.GetValue(obj);
return value == null ? 0 : value.GetHashCode();
}
Also you can get rid of Possible compare of value type with null warning from resharper using object.ReferenceEquals
GetHashCode parameter is named as type, which is misleading, So I renamed it to obj
Update:
If you need to use value comparison you must use Object.Equals method as opposed to using == operator for object type
public bool Equals(T obj1, T obj2)
{
if (ReferenceEquals(obj1, obj2))
{
return true;
}
if (ReferenceEquals(obj1, null) || ReferenceEquals(obj2, null))
{
return false;
}
return object.Equals(PropertyInfo.GetValue(obj1, null),
PropertyInfo.GetValue(obj2, null));
}

Generic solution to get default values of all properties in a range of objects

I want to get back the default values of all properties in a range of objects. The logic used is that if all of the property values in the range are the same, then use that for the default value, otherwise leave it null/type default.
I'm not sure if there is a better way to do this, but I'm open to all suggestions. I have created a working solution that is fairly generic, but I want it to be more so if possible. The current problem is that I have to have the if/elseif chain of the same code with a single difference of explicitly defining the type. I couldn't figure out how to get back the GetValue of the PropertyInfo and have the type properly pass into the generic functions. Once I got the object back, it would always pass down into the Generic as 'object' instead of 'int','decimal', etc. I also ran into the boxing/unboxing issue with nullables. I tried setting up the GetPropertyValue function with a generic return, but that requires you to pass in the type, which I'm not doing since I get it inside the function.
All of this code is just a working example. My classes have hundreds of properties and with 30 different classes, thats around 3000 properties I don't want to explicitly write out.
public class MainFunction
{
public MainFunction()
{
ParentClass defaultClass = new ParentClass();
List<ParentClass> results = MyDatabaseCallThatGetsBackListOfClass();
defaultClass = Generic.GetDefaultProperty(defaultClass, results);
}
private List<ParentClass> MyDatabaseCallThatGetsBackListOfClass()
{
List<ParentClass> populateResults = new List<ParentClass>();
for (int i = 0; i < 50; i++)
{
populateResults.Add(new ParentClass()
{
Class1 = new SubClass1()
{
Property1 = "Testing",
Property2 = DateTime.Now.Date,
Property3 = true,
Property4 = (decimal?)1.14,
Property5 = (i == 1 ? 5 : 25), // different, so should return null
Class1 = new SubSubClass1()
{
Property1 = "Test"
},
Class2 = new SubSubClass2()
},
Class2 = new SubClass2()
{
Property1 = null,
Property2 = 10,
Property3 = (i == 1 ? 15 : 30), // different, so should return null
Property4 = 20
}
});
}
return populateResults;
}
}
public class ParentClass
{
public ParentClass()
{
this.Class1 = new SubClass1();
this.Class2 = new SubClass2();
}
public SubClass1 Class1 { get; set; }
public SubClass2 Class2 { get; set; }
}
public class SubClass1
{
public SubClass1()
{
this.Class1 = new SubSubClass1();
this.Class2 = new SubSubClass2();
}
public string Property1 { get; set; }
public DateTime? Property2 { get; set; }
public bool? Property3 { get; set; }
public decimal? Property4 { get; set; }
public int? Property5 { get; set; }
public bool Property6 { get; set; }
public decimal Property7 { get; set; }
public DateTime Property8 { get; set; }
public int Property9 { get; set; }
public SubSubClass1 Class1 { get; set; }
public SubSubClass2 Class2 { get; set; }
}
public class SubClass2
{
public int? Property1 { get; set; }
public int? Property2 { get; set; }
public int Property3 { get; set; }
public int Property4 { get; set; }
}
public class SubSubClass1
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
public class SubSubClass2
{
public decimal? Property1 { get; set; }
public decimal Property2 { get; set; }
}
public static class Generic
{
public static T GetDefaultProperty<T>(T defaultItem, List<T> itemList)
where T : class
{
Type defaultType = defaultItem.GetType();
var props = defaultType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead);
foreach (var p in props)
{
if (p.PropertyType.IsClass && p.PropertyType != typeof(string))
{
dynamic classProperty = GetPropertyValue(defaultItem, p.Name);
var classList = GetClassSubList(itemList, classProperty, p.Name);
p.SetValue(defaultItem, GetDefaultProperty(classProperty, classList), null);
}
else
{
if (p.PropertyType == typeof(int?))
{
List<int?> subList = GetPropertySubList(itemList, TypeDefault<int?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(bool?))
{
List<bool?> subList = GetPropertySubList(itemList, TypeDefault<bool?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(decimal?))
{
List<decimal?> subList = GetPropertySubList(itemList, TypeDefault<decimal?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(DateTime?))
{
List<DateTime?> subList = GetPropertySubList(itemList, TypeDefault<DateTime?>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(string))
{
List<string> subList = GetPropertySubList(itemList, TypeDefault<string>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(int))
{
List<int> subList = GetPropertySubList(itemList, TypeDefault<int>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(bool))
{
List<bool> subList = GetPropertySubList(itemList, TypeDefault<bool>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(decimal))
{
List<decimal> subList = GetPropertySubList(itemList, TypeDefault<decimal>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
else if (p.PropertyType == typeof(DateTime))
{
List<DateTime> subList = GetPropertySubList(itemList, TypeDefault<DateTime>(), p.Name);
if (subList.Distinct().ToList().Count == 1)
{
p.SetValue(defaultItem, subList.FirstOrDefault(), null);
}
}
}
}
return defaultItem;
}
private static object GetPropertyValue<T>(T item, string propertyName)
{
if (item == null || string.IsNullOrEmpty(propertyName))
{
return null;
}
PropertyInfo pi = item.GetType().GetProperty(propertyName);
if (pi == null)
{
return null;
}
if (!pi.CanRead)
{
return null;
}
return pi.GetValue(item, null);
}
private static List<TReturn> GetClassSubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
where T : class
where TReturn : class
{
return list.Select(GetClassSelection<T, TReturn>(propertyName)).ToList();
}
private static Func<T, TReturn> GetClassSelection<T, TReturn>(string fieldName)
where T : class
where TReturn : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
var body = Expression.Property(p, fieldName);
return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
}
private static List<TReturn> GetPropertySubList<T, TReturn>(List<T> list, TReturn returnType, string propertyName)
where T : class
{
return list.Select(GetPropertySelection<T, TReturn>(propertyName)).ToList();
}
private static Func<T, TReturn> GetPropertySelection<T, TReturn>(string fieldName)
where T : class
{
ParameterExpression p = Expression.Parameter(typeof(T), "t");
var body = Expression.Property(p, fieldName);
return Expression.Lambda<Func<T, TReturn>>(body, new ParameterExpression[] { p }).Compile();
}
private static T TypeDefault<T>()
{
return default(T);
}
You can switch the huge IF statement block with this:
var result = itemList.Select(x => p.GetValue(x, null)).Distinct();
if (result.Count() == 1)
{
p.SetValue(defaultItem, result.First(), null);
}
If you use Distinct(), references/value types are compared using object.Equals that first tests reference equality, and later actual values. This method has only one draw-back: boxing/unboxing. Use that code for reference types.
Note: there's already a lot of boxing happening in you code.
Reflection is based on a "object", so it's pretty hard not to have boxing issues.
For example:
Type defaultType = defaultItem.GetType(); // boxing on value types.
p.SetValue(defaultItem, subList.FirstOrDefault(), null); // boxing
Boxing is minor cost with reflection. You can run benchmarks to check.
As for your actual problem; you have list of objects and you want to compare them all, recursively. If there is no difference between two objects, you want to set the property in defaultItem to a property value that all objects share.
Ignoring the reason for whatever reason you're doing this(since I don't care; rather the solution to this problem is interesting), let's continue :P
Your best bet is to generate strongly-typed comparer on startup using reflection. Generate the code using StringBuilder and later use CSharpCodeProvider() to compile from StringBuilder and return strongly-typed delegate that has no reflection overhead. This is the fastest one I can think of, right now. The only hit it will take is the first interrogation of reflection metadata on startup. It's only per T.
On production code, you can cache that strongly typed comparer onto DLL, so the hit will be only one-time event.
private static class StrongClassComparer<T>
{
public static Func<T, string> GenerateMethod()
{
var code = new StringBuilder();
// generate your STRONGLY-TYPED code here.
// into code variable.
code.Append("public class YourClassInCode { "+
" public static string YourClassStaticMethod("+ typeof(T).Name+ " test)" +
" { return string.Empty; } }");
var compiler = new CSharpCodeProvider();
var parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(typeof (T).Assembly.Location);
parameters.CompilerOptions = "/optimize + ";
var results = compiler.CompileAssemblyFromSource(parameters, code.ToString());
var #class = results.CompiledAssembly.GetType("YourClassInCode");
var method = #class.GetMethod("YourClassStaticMethod");
return (Func<T, string>) Delegate.CreateDelegate(typeof (Func<T, string>), method);
}
}

Categories

Resources