Enumerable.Except does not use my custom comparer - c#

I try to use the except method with a custom equality comparer, but it is not work.
My equality comparer:
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject
{
#region IEqualityComparer<T> Members
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <param name="x">The first object of type <paramref name="T"/> to compare.</param>
/// <param name="y">The second object of type <paramref name="T"/> to compare.</param>
/// <returns>
/// <see langword="true"/> If the specified objects are equal; otherwise, <see langword="false"/>.
/// </returns>
public bool Equals(T x, T y)
{
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <param name="obj">The object to get the hash code.</param>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
/// </exception>
public int GetHashCode(T obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
My except usage:
BusinessObjectGuidEqualityComparer<Area> comparer = new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = this.Areas.Except(allocatedAreas, comparer);
IEnumerable<Area> toAdd = allocatedAreas.Except(this.Areas, comparer);
The strange thing is, event I provide my custom equality comparer the default one is used, so what do I make wrong?
Thanks for help.

Similar to Marc I just tested this, everything is being called just fine, my guess is that you are caught by the LINQ deferred execution, notice the ToArray in my code.
Note, when tracing through this I noticed GetHashCode is never called on null objects in the comparer.
Keep in mind, MiscUtil has an awesome way for you to do this stuff inline, see: Can I specify my explicit type comparator inline?
Or you could adapt this to Except: Distinct list of objects based on an arbitrary key in LINQ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
public class BusinessObject {
public Guid Guid { get; set; }
}
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject {
#region IEqualityComparer<T> Members
public bool Equals(T x, T y) {
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// </exception>
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
class Program {
static void Main(string[] args) {
var comparer = new BusinessObjectGuidEqualityComparer<BusinessObject>();
List<BusinessObject> list1 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()}
};
List<BusinessObject> list2 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()},
null,
null,
list1[0]
};
var toRemove = list1.Except(list2, comparer).ToArray();
var toAdd = list2.Except(list1, comparer).ToArray();
// toRemove.Length == 1
// toAdd.Length == 2
Console.ReadKey();
}
}
}

Try:
public int GetHashCode(T obj) {
return obj == null ? 0 : obj.Guid.GetHashCode();
}
Your hash-code must match equality (or at least, not contradict it); and your equality says "nulls are equal, otherwise compare the guid". Internally, I expect Except uses a HashSet<T>, which explains why getting GetHashCode right is so important.
Here's my test rig (using the above GetHashCode) which works fine:
public abstract class BusinessObject {
public Guid Guid { get; set; }
}
class Area : BusinessObject {
public string Name { get; set; }
static void Main() {
Guid guid = Guid.NewGuid();
List<Area> areas = new List<Area> {
new Area { Name = "a", Guid = Guid.NewGuid() },
new Area { Name = "b", Guid = guid },
new Area { Name = "c", Guid = Guid.NewGuid() },
};
List<Area> allocatedAreas = new List<Area> {
new Area { Name = "b", Guid = guid},
new Area { Name = "d", Guid = Guid.NewGuid()},
};
BusinessObjectGuidEqualityComparer<Area> comparer =
new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = areas.Except(allocatedAreas, comparer);
foreach (var row in toRemove) {
Console.WriteLine(row.Name); // shows a & c, since b is allocated
}
}
}
If your version doesn't work, you're going to have to post something about how you're using it, as it works fine for me (above).

The methods in your equality comparer doesn't match up. You are comparing the GUID of the objects, but the GetHashCode method uses the default implementation which is based on the reference, not the GUID. As different instances will get different hash codes eventhough they have the same GUID, the Equals method will never be used.
Get the hash code for the GUID in the GetHashCode method, as that is what you are comparing:
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.Guid.GetHashCode();
}

Related

how to copy properties from one object to another with different values C#

I want to copy the values of Properties in a given object ClassA to another object instance called ClassB, these classes may or may not be the same type.
if a property in ClassB has a value and in ClassA the corresponding property value is null, then do not copy that value, so only copy across where the current property in ClassB is null.
This is NOT a clone exercise, the target object (ClassB) is already instantiated with partially defined values, I'm looking for a reusable way to copy across the rest of the values that were not already set.
Think of testing scenarios where we have a common or default test data value, for specific tests I want to set some specific fields, then finish setting the other properties from the common test data object.
I think I am looking for a Reflection based solution, as that way we would not need to know the specific types to copy, which would make it reusable for many different scenarios.
eg.
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public Address ContactAddress { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
test eg.
public void TestMethod1()
{
Employee employee = new Employee();
employee.EmployeeID = 100;
employee.EmployeeName = "John";
employee.ContactAddress = new Address();
employee.ContactAddress.Address1 = "Park Ave";
employee.ContactAddress.City = "New York";
employee.ContactAddress.State = "NewYork";
employee.ContactAddress.ZipCode = "10002";
Employee employeeCopy = new Employee();
employeeCopy.EmployeeID = 101;
employeeCopy.EmployeeName = "Tom";
employeeCopy.ContactAddress = new Address();
CopyPropertiesTo(employee, employeeCopy);
}
I want to get the result
employeeCopy EmployeeID=101;
EmployeeName="Tom";
ContactAddress.Address1 = "Park Ave";
ContactAddress.City = "New York";
ContactAddress.State = "NewYork";
ContactAddress.ZipCode = "10002"
So in this case, because none of the fields in employeeCopy.ContactAddress have been set, only those fields from the original employee object should be copied across.
I can not figure out how to write the method:
CopyPropertiesTo(object sourceObject, object targetObject)
One way to do this is to simply check each property in the "to" Employee, and if it's null or 0, assign it the value from the "from" Employee:
/// <summary>
/// Copies values in 'from' to 'to' if they are null in 'to'
/// </summary>
public static void CopyProperties(Employee from, Employee to)
{
if (from == null) return;
if (to == null) to = new Employee();
if (to.EmployeeID == 0) to.EmployeeID = from.EmployeeID;
if (to.EmployeeName == null) to.EmployeeName = from.EmployeeName;
if (from.ContactAddress == null) return;
if (to.ContactAddress == null) to.ContactAddress = new Address();
if (to.ContactAddress.Address1 == null)
to.ContactAddress.Address1 = from.ContactAddress.Address1;
if (to.ContactAddress.City == null)
to.ContactAddress.City = from.ContactAddress.City;
if (to.ContactAddress.State == null)
to.ContactAddress.State = from.ContactAddress.State;
if (to.ContactAddress.ZipCode == null)
to.ContactAddress.ZipCode = from.ContactAddress.ZipCode;
}
Here is my suggestions too if not too late, but mayby helps.
public class Source
{
[DefaultValueAttribute(-1)]
public int Property { get; set; }
public int AnotherProperty { get; set; }
}
public class Dedstination
{
public int Property { get; set; }
[DefaultValueAttribute(42)]
public int AnotherProperty { get; set; }
}
public void Main()
{
var source = new Source { Property = 10, AnotherProperty = 76 };
var destination = new Dedstination();
MapValues(source, destination);
}
public static void MapValues<TS, TD>(TS source, TD destination)
{
var srcPropsWithValues = typeof(TS)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(x => x.Name, y => y.GetValue(source));
var dstProps = typeof(TD)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(key => key, value => value.GetCustomAttribute<DefaultValueAttribute>()?.Value
?? (value.PropertyType.IsValueType
? Activator.CreateInstance(value.PropertyType, null)
: null));
foreach (var prop in dstProps)
{
var destProperty = prop.Key;
if (srcPropsWithValues.ContainsKey(destProperty.Name))
{
var defaultValue = prop.Value;
var currentValue = destProperty.GetValue(destination);
var sourceValue = srcPropsWithValues[destProperty.Name];
if (currentValue.Equals(defaultValue) && !sourceValue.Equals(defaultValue))
{
destProperty.SetValue(destination, sourceValue);
}
}
}
}
EDIT: I edited my solution in order to remove the dependency on using DefaultValueAttribute. Now you can take a default value either from the attributes if specified or the type defaults.
Previous solution was as follows:
// This solution do not needs DefaultValueAttributes
var dstProps = typeof(TD)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(x => x, x => x.PropertyType.IsValueType ? Activator.CreateInstance(x.PropertyType, null) : null);
// This solution needs DefaultValueAttributes
var dstProps = typeof(TD)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(x => x, x => x.GetCustomAttribute<DefaultValueAttribute>()?.Value ?? null);
Deep cloning can be achieved easily through serialization, however to only copy across non-null fields needs more conditional logic, In this case I call this a Coalesce so I've named my Method CoalesceTo. You could refactor this into an extension method if you wanted to, but I wouldn't recommend it, instead put this inside a static helper class. As useful as this might be, I don't encourage it as your "goto" for a production business runtime.
Using Reflection for these types of solutions is usually the most inefficient mechanism, but it gives us a lot of flexibility and is great for mocking, prototyping and quick unit test expressions.
Although not in this example, it would be easy to add in checks to exclude [Obsolete] properties for advanced scenarios
The following example uses Property Name comparison, so you don't have to pass in objects of the same type. Notice that IsNull and IsValueType methods have been created to encapsulate those concepts, simplifying tweaks you might want to make to this method.
This method also checks that properties can be read/written before proceeding, which allows us to support readonly properties on the source object, and of course we don't try to write to readonly properties.
The final value parse and write is wrapped in a try catch statement that is suppressing any errors, It takes a bit of tweaking to get code like this to work universally, but it should work fine for simple type definitions.
/// <summary>
/// Deep Copy the top level properties from this object only if the corresponding property on the target object IS NULL.
/// </summary>
/// <param name="source">the source object to copy from</param>
/// <param name="target">the target object to update</param>
/// <returns>A reference to the Target instance for chaining, no changes to this instance.</returns>
public static void CoalesceTo(object source, object target, StringComparison propertyComparison = StringComparison.OrdinalIgnoreCase)
{
var sourceType = source.GetType();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
foreach(var sourceProp in sourceType.GetProperties())
{
if(sourceProp.CanRead)
{
var sourceValue = sourceProp.GetValue(source);
// Don't copy across nulls or defaults
if (!IsNull(sourceValue, sourceProp.PropertyType))
{
var targetProp = targetProperties.FirstOrDefault(x => x.Name.Equals(sourceProp.Name, propertyComparison));
if (targetProp != null && targetProp.CanWrite)
{
if (!targetProp.CanRead)
continue; // special case, if we cannot verify the destination, assume it has a value.
else if (targetProp.PropertyType.IsArray || targetProp.PropertyType.IsGenericType // It is ICollection<T> or IEnumerable<T>
&& targetProp.PropertyType.GenericTypeArguments.Any()
&& targetProp.PropertyType.GetGenericTypeDefinition() != typeof(Nullable<>) // because that will also resolve GetElementType!
)
continue; // special case, skip arrays and collections...
else
{
// You can do better than this, for now if conversion fails, just skip it
try
{
var existingValue = targetProp.GetValue(target);
if (IsValueType(targetProp.PropertyType))
{
// check that the destination is NOT already set.
if (IsNull(existingValue, targetProp.PropertyType))
{
// we do not overwrite a non-null destination value
object targetValue = sourceValue;
if (!targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
{
// TODO: handle specific types that don't go across.... or try some brute force type conversions if neccessary
if (targetProp.PropertyType == typeof(string))
targetValue = targetValue.ToString();
else
targetValue = Convert.ChangeType(targetValue, targetProp.PropertyType);
}
targetProp.SetValue(target, targetValue);
}
}
else if (!IsValueType(sourceProp.PropertyType))
{
// deep clone
if (existingValue == null)
existingValue = Activator.CreateInstance(targetProp.PropertyType);
CoalesceTo(sourceValue, existingValue);
}
}
catch (Exception)
{
// suppress exceptions, don't set a field that we can't set
}
}
}
}
}
}
}
/// <summary>
/// Check if a boxed value is null or not
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of null in here.
/// </remarks>
/// <param name="value">Value to inspect</param>
/// <param name="valueType">Type of the value, pass it in if you have it, otherwise it will be resolved through reflection</param>
/// <returns>True if the value is null or primitive default, otherwise False</returns>
public static bool IsNull(object value, Type valueType = null)
{
if (value is null)
return true;
if (valueType == null) valueType = value.GetType();
if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType)
{
// Handle nullable types like float? or Nullable<Int>
if (valueType.IsGenericType)
return value is null;
else
return Activator.CreateInstance(valueType).Equals(value);
}
// treat empty string as null!
if (value is string s)
return String.IsNullOrWhiteSpace(s);
return false;
}
/// <summary>
/// Check if a type should be copied by value or if it is a complexe type that should be deep cloned
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of Object vs Value/Primitive here.
/// </remarks>
/// <param name="valueType">Type of the value to check</param>
/// <returns>True if values of this type can be straight copied, false if they should be deep cloned</returns>
public static bool IsValueType(Type valueType)
{
// TODO: any specific business types that you want to treat as value types?
// Standard .Net Types that can be treated as value types
if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType || valueType == typeof(string))
return true;
// Support Nullable Types as Value types (Type.IsValueType) should deal with this, but just in case
if (valueType.HasElementType // It is array/enumerable/nullable
&& valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))
return true;
return false;
}
Because we are using reflection here, we cant take advantage of optimisations that Generics could offer us. If you wanted to adapt this to a production environment, consider using T4 templates to script out a Generic typed version of this logic as extension methods to your business types.
Deep Cloning -
You'll notice I specifically skip arrays and other IEnumerable structures... There's a whole can of worms in supporting them, it might be better to not let the one method attempt a Deep copy, so take the nested call to CoalesceTo out, then call the clone method on each object in the tree.
The problem with arrays/collections/lists is that before you could clone, you would need to identify a way to synchronise the collection in the source with the collection in the target, you could make a convention based on an Id field or some kind of attribute like [KeyAttribute] but that sort of implementation needs to be highly specific to your business logic and is outside of the scope of this already monstrous post ;)
Types like Decimal and DateTime are problematic in these types of scenarios, they should not be compared to null, instead we have to compare them to their default type states, again we can't use the generic default operator or value in this case because the type can only be resolved at runtime.
So I've changed your classes to include an example of how DateTimeOffset is handled by this logic:
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public DateTimeOffset Date { get; set; }
public float? Capacity { get; set; }
Nullable<int> MaxShift { get; set; }
public Address ContactAddress { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
public static void TestMethod1()
{
Employee employee = new Employee();
employee.EmployeeID = 100;
employee.EmployeeName = "John";
employee.Capacity = 26.2f;
employee.MaxShift = 8;
employee.Date = new DateTime(2020,1,22);
employee.ContactAddress = new Address();
employee.ContactAddress.Address1 = "Park Ave";
employee.ContactAddress.City = "New York";
employee.ContactAddress.State = "NewYork";
employee.ContactAddress.ZipCode = "10002";
Employee employeeCopy = new Employee();
employeeCopy.EmployeeID = 101;
employeeCopy.EmployeeName = "Tom";
employeeCopy.ContactAddress = new Address();
CoalesceTo(employee, employeeCopy);
}
This results in the following object graph:
{
"EmployeeID": 101,
"EmployeeName": "Tom",
"Date": "2020-01-22T00:00:00+11:00",
"Capacity":26.2,
"MaxShift":8,
"ContactAddress": {
"Address1": "Park Ave",
"City": "New York",
"State": "NewYork",
"ZipCode": "10002"
}
}
Make your changes to the new instance after the copy completes and/or implement ICloneable interface.
https://learn.microsoft.com/en-us/dotnet/api/system.icloneable?view=netcore-3.1
private Employee Check(Employee employee,Employee employeeCopy)
{
if (employeeCopy.EmployeeID==0 && employee.EmployeeID !=0)
{
employeeCopy.EmployeeID = employee.EmployeeID;
}
if (employeeCopy.EmployeeName == null && employee.EmployeeName != null)
{
employeeCopy.EmployeeName = employee.EmployeeName;
}
if (employeeCopy.ContactAddress == null)
{
if (employeeCopy.ContactAddress.Address1 == null && employee.ContactAddress.Address1 != null)
{
employeeCopy.ContactAddress.Address1 = employee.ContactAddress.Address1;
}
if (employeeCopy.ContactAddress.City == null && employee.ContactAddress.City != null)
{
employeeCopy.ContactAddress.City = employee.ContactAddress.City;
}
if (employeeCopy.ContactAddress.State == null && employee.ContactAddress.State != null)
{
employeeCopy.ContactAddress.State = employee.ContactAddress.State;
}
if (employeeCopy.ContactAddress.ZipCode == null && employee.ContactAddress.ZipCode != null)
{
employeeCopy.ContactAddress.ZipCode = employee.ContactAddress.ZipCode;
}
}
return employeeCopy;
}
Is this what you are looking for ?
public static void CopyPropertiesTo(Employee EP1, Employee EP2){
Type eType=typeof(Employee);
PropertyInfo[] eProps = eType.GetProperties();
foreach(var p in eProps){
if(p.PropertyType != typeof(String) && p.PropertyType != typeof(Int32)){
//Merging Contact Address
Type cType=p.PropertyType;
PropertyInfo[] cProps = cType.GetProperties();
foreach(var c in cProps){
//Check if value is null
if (String.IsNullOrEmpty((EP2.ContactAddress.GetType().GetProperty(c.Name).GetValue(EP2.ContactAddress) as string))){
//Assign Source to Target
EP2.ContactAddress.GetType().GetProperty(c.Name).SetValue(EP2.ContactAddress, (EP1.ContactAddress.GetType().GetProperty(c.Name).GetValue(EP1.ContactAddress)));
}
}
}
else{
//Check if value is null or empty
if (String.IsNullOrEmpty((EP2.GetType().GetProperty(p.Name).GetValue(EP2) as string))){
//Assign Source to Target
EP2.GetType().GetProperty(p.Name).SetValue(EP2, (EP1.GetType().GetProperty(p.Name).GetValue(EP1)));
}
}
}
}
Not the prettiest, but this should do it and allow you to change the names/amount of properties in the class. I haven't ever actually tried doing it like this so if someone out there has some feedback, I would appreciate it
Check out the following links for more info and examples
PropertyInfo
GetType
GetProperty
Instead of trying to do a deep copy at all, these types of issues are generally easier and less resource intensive if you do a full deep clone first, and then set your values.
There are many posts on SO regarding deep clone, my preference is just to use JSON.Net to serialize and then deserialize.
public static T Clone<T>(T value, Newtonsoft.Json.JsonSerializerSettings settings = null)
{
var objectType = value.GetType();
var cereal = Newtonsoft.Json.JsonConvert.SerializeObject(value, settings);
return (T)Newtonsoft.Json.JsonConvert.DeserializeObject(cereal, objectType, settings);
}
However this code requires the Newtonsoft.Json nuget package reference.
Cloning the object sets all the common/default values first, and then we only modify those properties that we need for this specific test, or code block.
public void TestMethod1()
{
Employee employee = new Employee();
employee.EmployeeID = 100;
employee.EmployeeName = "John";
employee.ContactAddress = new Address();
employee.ContactAddress.Address1 = "Park Ave";
employee.ContactAddress.City = "New York";
employee.ContactAddress.State = "NewYork";
employee.ContactAddress.ZipCode = "10002";
// Create a deep clone of employee
Employee employeeCopy = Clone(employee);
// set the specific fields that we want to change
employeeCopy.EmployeeID = 101;
employeeCopy.EmployeeName = "Tom";
}
Often we can find simpler solutions if we are open to changing our approach, this solution will have the same output as if we had conditionally copied across the property values, but without comparing anything.
If you have other reasons for a conditional copy, referred to in other solutions to this post as a Merge or Coalesce, then my other answer using reflection will do the job, but its not as robust as this one.
[TestClass]
public class UnitTest11
{
[TestMethod]
public void TestMethod1()
{
Employee employee = new Employee();
employee.EmployeeID = 100;
employee.EmployeeName = "John";
employee.Date = DateTime.Now;
employee.ContactAddress = new Address();
employee.ContactAddress.Address1 = "Park Ave";
employee.ContactAddress.City = "New York";
employee.ContactAddress.State = "NewYork";
employee.ContactAddress.ZipCode = "10002";
Employee employeeCopy = new Employee();
employeeCopy.EmployeeID = 101;
employeeCopy.EmployeeName = "Tom";
employeeCopy.ContactAddress = new Address();
employeeCopy.ContactAddress.City = "Bei Jing";
//copy all properties from employee to employeeCopy
CoalesceTo(employee, employeeCopy);
Console.ReadLine();
}
/// Deep Copy the top level properties from this object only if the corresponding property on the target object IS NULL.
/// </summary>
/// <param name="source">the source object to copy from</param>
/// <param name="target">the target object to update</param>
/// <returns>A reference to the Target instance for chaining, no changes to this instance.</returns>
public static void CoalesceTo(object source, object target, StringComparison propertyComparison = StringComparison.OrdinalIgnoreCase)
{
var sourceType = source.GetType();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
foreach (var sourceProp in sourceType.GetProperties())
{
if (sourceProp.CanRead)
{
var sourceValue = sourceProp.GetValue(source);
// Don't copy across nulls or defaults
if (!IsNull(sourceValue, sourceProp.PropertyType))
{
var targetProp = targetProperties.FirstOrDefault(x => x.Name.Equals(sourceProp.Name, propertyComparison));
if (targetProp != null && targetProp.CanWrite)
{
if (!targetProp.CanRead)
continue; // special case, if we cannot verify the destination, assume it has a value.
else if (targetProp.PropertyType.IsArray || targetProp.PropertyType.IsGenericType // It is ICollection<T> or IEnumerable<T>
&& targetProp.PropertyType.GenericTypeArguments.Any()
&& targetProp.PropertyType.GetGenericTypeDefinition() != typeof(Nullable<>) // because that will also resolve GetElementType!
)
continue; // special case, skip arrays and collections...
else
{
// You can do better than this, for now if conversion fails, just skip it
try
{
var existingValue = targetProp.GetValue(target);
if (IsValueType(targetProp.PropertyType))
{
// check that the destination is NOT already set.
if (IsNull(existingValue, targetProp.PropertyType))
{
// we do not overwrite a non-null destination value
object targetValue = sourceValue;
if (!targetProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType))
{
// TODO: handle specific types that don't go across.... or try some brute force type conversions if neccessary
if (targetProp.PropertyType == typeof(string))
targetValue = targetValue.ToString();
else
targetValue = Convert.ChangeType(targetValue, targetProp.PropertyType);
}
targetProp.SetValue(target, targetValue);
}
}
else if (!IsValueType(sourceProp.PropertyType))
{
// deep clone
if (existingValue == null)
existingValue = Activator.CreateInstance(targetProp.PropertyType);
CoalesceTo(sourceValue, existingValue);
}
}
catch (Exception)
{
// suppress exceptions, don't set a field that we can't set
}
}
}
}
}
}
}
/// <summary>
/// Check if a boxed value is null or not
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of null in here.
/// </remarks>
/// <param name="value">Value to inspect</param>
/// <param name="valueType">Type of the value, pass it in if you have it, otherwise it will be resolved through reflection</param>
/// <returns>True if the value is null or primitive default, otherwise False</returns>
public static bool IsNull(object value, Type valueType = null)
{
if (value is null)
return true;
if (valueType == null) valueType = value.GetType();
if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType)
return value.Equals(Activator.CreateInstance(valueType));
// treat empty string as null!
if (value is string s)
return String.IsNullOrWhiteSpace(s);
return false;
}
/// <summary>
/// Check if a type should be copied by value or if it is a complexe type that should be deep cloned
/// </summary>
/// <remarks>
/// Evaluate your own logic or definition of Object vs Value/Primitive here.
/// </remarks>
/// <param name="valueType">Type of the value to check</param>
/// <returns>True if values of this type can be straight copied, false if they should be deep cloned</returns>
public static bool IsValueType(Type valueType)
{
// TODO: any specific business types that you want to treat as value types?
// Standard .Net Types that can be treated as value types
if (valueType.IsPrimitive || valueType.IsEnum || valueType.IsValueType || valueType == typeof(string))
return true;
// Support Nullable Types as Value types (Type.IsValueType) should deal with this, but just in case
if (valueType.HasElementType // It is array/enumerable/nullable
&& valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(Nullable<>))
return true;
return false;
}
}
public class Employee
{
public int EmployeeID { get; set; }
public string EmployeeName { get; set; }
public DateTimeOffset Date { get; set; }
public float? check { get; set; }
public Address ContactAddress { get; set; }
}
public class Address
{
public string Address1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
}
Thank you very much for every one,especially for #Chris Schaller I post the code above

Compare object in custom list and return the unmatched object C#

I have two models
public class NewrecordModel
{
public string NewName { get; set; }
public string NewFileName { get; set; }
public string NewFileVersion { get; set; }
}
public class OldrecordModel
{
public string OldName { get; set; }
public string OldFileName { get; set; }
public string OldFileVersion { get; set; }
}
I need to compare the property, NewName with OldName and NewFileVersion with OldFileVersion and return the difference from NewrecordModel in a list.
I tried the below one,
var unMatchedRecord = NewrecordModel.Where(o => !OldrecordModel.Any(n => n.OldName == o.NewName) || !OldrecordModel.Any(n => n.OldFileVersion == o.NewFileVersion));
The above one is returning the unmatch NewrecordModel in a list and it is working fine, but i need to compare OldFileVersion is less than NewFileVersion and return the list now. While using OldFileVersion is less than NewFileVersion in query it is listing unrelated data as output.
Below is the query i tried to compare,
var unMatchedVersion = NewrecordModel.Where(o => OldrecordModel.Any(n => n.OldFileVersion.ToInt() < o.NewFileVersion.ToInt()));
Is the above linq is correct. How to compare the numbers in linq and return the result.
I wrote this for get properties name and value what changed. I hope this help you.
public static class CompareObject
{
/// <summary>
/// Compares the properties of two objects of the same type and returns if all properties are equal.
/// </summary>
/// <param name="objectA">The first object to compare.</param>
/// <param name="objectB">The second object to compre.</param>
/// <param name="ignoreList">A list of property names to ignore from the comparison.</param>
/// <returns><c>true</c> if all property values are equal, otherwise <c>false</c>.</returns>
public static Dictionary<string,object> AreObjectsEqual(object objectA, object objectB, params string[] ignoreList)
{
//bool result;
var list = new Dictionary<string, object>();
if (objectA != null && objectB != null)
{
Type objectType;
objectType = objectA.GetType();
//result = true; // assume by default they are equal
foreach (PropertyInfo propertyInfo in objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && !ignoreList.Contains(p.Name)))
{
// if it is a primative type, value type or implements IComparable, just directly try and compare the value
if (CanDirectlyCompare(propertyInfo.PropertyType))
{
object valueA;
object valueB;
valueA = propertyInfo.GetValue(objectA, null);
valueB = propertyInfo.GetValue(objectB, null);
if (!AreValuesEqual(valueA, valueB))
{
list.Add(propertyInfo.Name, valueA);
//Console.WriteLine("Mismatch with property '{0}.{1}' found.", objectType.FullName, propertyInfo.Name);
//result = false;
}
}
}
}
//else
// result = object.Equals(objectA, objectB);
return list;
}
/// <summary>
/// Determines whether value instances of the specified type can be directly compared.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if this value instances of the specified type can be directly compared; otherwise, <c>false</c>.
/// </returns>
private static bool CanDirectlyCompare(Type type)
{
return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
/// <summary>
/// Compares two values and returns if they are the same.
/// </summary>
/// <param name="valueA">The first value to compare.</param>
/// <param name="valueB">The second value to compare.</param>
/// <returns><c>true</c> if both values match, otherwise <c>false</c>.</returns>
private static bool AreValuesEqual(object valueA, object valueB)
{
bool result;
IComparable selfValueComparer;
selfValueComparer = valueA as IComparable;
if (valueA == null && valueB != null || valueA != null && valueB == null)
result = false; // one of the values is null
else if (selfValueComparer != null && selfValueComparer.CompareTo(valueB) != 0)
result = false; // the comparison using IComparable failed
else if (!object.Equals(valueA, valueB))
result = false; // the comparison using Equals failed
else
result = true; // match
return result;
}
}
and to use
var changedColumns = CompareObject.AreObjectsEqual(NewrecordModel, OldrecordModel);
It might be an object from NewrecordModel collection, for which there are no objects in OldrecordModel collection with the same name OR any identically named object from OldrecordModel collection has a smaller OldFileVersion
Check the codes below:
var unMatchedVersion =
newrecordModel.Where(o => !oldrecordModel.Any(n => n.OldName == o.NewName) ||
oldrecordModel.Any(n => n.OldName == o.NewName &&
int.Parse(n.OldFileVersion) < int.Parse(o.NewFileVersion)));

How to recursively search for string term in a list of obects

I'm trying to implement a way of searching a list of objects for some search term and then returning those objects.
So far, I have managed to get it working if the search term is contained in any of the object's string properties:
IEnumerableExtensions
public static IEnumerable<T> Search<T>(this IEnumerable<T> items, string search)
{
if (!string.IsNullOrEmpty(search))
items = items.Where(i => i.Contains(search));
return items;
}
ObjectExtensions
public static bool Contains(this object inuputObject, string word)
{
return inuputObject.GetType()
.GetProperties()
.Where(x => x.PropertyType == typeof(string))
.Select(x => (string)x.GetValue(inuputObject, null))
.Where(x => x != null)
.Any(x => x.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0);
}
The problem is, the objects I'm searching each contain a list of user objects, and I want to include the string properties of those users in my search.
I tried this:
public static bool Contains(this object inuputObject, string word)
{
var result = false;
var type = inuputObject.GetType();
var properties = type.GetProperties();
foreach (var property in properties)
{
if (property.PropertyType == typeof(string) && property != null)
{
var propertyValue = (string)property.GetValue(inuputObject, null);
result = propertyValue.IndexOf(word, StringComparison.CurrentCultureIgnoreCase) >= 0;
}
else
{
result = property.Contains(word);
}
if (result)
break;
}
return result;
}
But I think this is iterating around properties which I'm not interested in, and it causes the program to crash with this message in VS:
The application is in break mode
Your app has entered a break state, but there is no code to show because all threads were executing external code (typically system or framework code).
I've never seen such an error before, but I suspect it has something to do with the code running into an infinite loop, as it is checking the properties of the object, then the properties of those properties etc etc - where would that stop?
Does anyone have any suggestions for how I can achieve this?
Thank you
Your recursive call checks if the property object contains the word, not if the value of that property on your original object contains the word.
Change
result = property.Contains(word);
to
result = property.GetValue(inuputObject, null).Contains(word);
My final solution looks like this
ObjectExtensions.cs
public static class ObjectExtensions
{
/// <summary>
/// Checks each string property of the given object to check if it contains the
/// search term. If any of those properties is a collection, we search that
/// collection using the the IEnumarableExtensions Search
/// </summary>
/// <param name="inputObject"></param>
/// <param name="term"></param>
/// <returns></returns>
public static bool Contains(this object inputObject, string term)
{
var result = false;
if (inputObject == null)
return result;
var properties = inputObject
.GetType()
.GetProperties();
foreach (var property in properties)
{
// First check if the object is a string (and ensure it is not null)
if (property != null && property.PropertyType == typeof(string))
{
var propertyValue = (string)property.GetValue(inputObject, null);
result = propertyValue == null
? false
: propertyValue.IndexOf(term,
StringComparison.CurrentCultureIgnoreCase) >= 0;
}
// Otherwise, check if its a collection (we need to do this after the string
// check, as a string is technically a IEnumerable type
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
{
result = ((IEnumerable<object>)property
.GetValue(inputObject, null))
.Search(term).Count() > 0;
}
else
{
var propertyValue = property.GetValue(inputObject, null);
result = propertyValue == null
? false
: propertyValue.ToString().Contains(term);
}
if (result)
break;
}
return result;
}
}
IEnumerableExtensions.cs
public static class IEnumerableExtensions
{
/// <summary>
/// Extension method that searches a list of generic objects' string properties
/// for the given search term using the 'Contains' object extension
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="items"></param>
/// <param name="search"></param>
/// <returns></returns>
public static IEnumerable<T> Search<T>(this IEnumerable<T> items, string search)
{
if (!string.IsNullOrEmpty(search))
items = items.Where(i => i.Contains(search));
return items;
}
}
Usage
So to search a collection of objects for some string:
var list = new List<MyType>(){};
var results = list.Search("searchTerm");

OData Delta Patch Security

I have a working PATCH for my user class with Delta in Web API 2. By using the .patch method I can easily detect only the changes that were sent over and then update accordingly, rather than have to receive the entire user!
The problem is there are several fields that I want to protect so they are never updated.
I saw one example on SO but it didn't leverage Delta rather seemed to be slightly more dated and practically wrote all of the patch code by hand. Is there not a way to easily tell OData's patch to skip over properties you designate (maybe I need to override patch and tell it to avoid some properties)?
How would I even begin to go about doing this (or what should I search for / research to get started)? Do action filters / validation have a role here? Do I look into model binding? Is it overriding patch?
Thanks!
Depending on what you want to do if someone tries to update protected fields you can either:
Update only fields that can be modified. For this you can construct new Delta with only these fields like this:
Delta<User> filteredDelta = new Delta<User>();
if (originalDelta.GetChangedPropertyNames().Contains("FirstName"))
{
filteredDelta.TrySetPropertyValue("FirstName", originalDelta.GetEntity().FirstName);
}
if (originalDelta.GetChangedPropertyNames().Contains("LastName"))
{
filteredDelta.TrySetPropertyValue("LastName", originalDelta.GetEntity().LastName);
}
filteredDelta.Patch(selectedUser);
Fail the PATCH request (I would say this is preferred and least surprising way to deal with such requests). This would be even simpler:
if (originalDelta.GetChangedPropertyNames().Contains("ModifiedDate"))
{
return InternalServerError(new ArgumentException("Attribue is read-only", "ModifiedDate"));
}
There's a couple of possibilities, depending on you use case...
You want to exclude the changes if they are supplied
You want to throw an error if non-editable fields are updated.
Start with an attribute to mark appropriate properties
/// <summary>
/// Marks a property as non-editable.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NonEditableAttribute : Attribute
{
}
Then we can write some extensions against Delta to take advantage of this
public static class PatchExtensions
{
/// <summary>
/// Get the properties of a type that are non-editable.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IList<string> NonEditableProperties(this Type type)
{
return type.GetProperties().Where(x => Attribute.IsDefined(x, typeof(NonEditableAttribute))).Select(prop => prop.Name).ToList();
}
/// <summary>
/// Get this list of non-editable changes in a <see cref="Delta{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static IList<string> NonEditableChanges<T>(this Delta<T> delta)
where T : class
{
var nec = new List<string>();
var excluded = typeof(T).NonEditableProperties();
nec.AddRange(delta.GetChangedPropertyNames().Where(x => excluded.Contains(x)));
return nec;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> based on a list of property names
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <param name="excluded"></param>
/// <returns></returns>
public static Delta<T> Exclude<T>(this Delta<T> delta, IList<string> excluded)
where T : class
{
var changed = new Delta<T>();
foreach (var prop in delta.GetChangedPropertyNames().Where(x => !excluded.Contains(x)))
{
object value;
if (delta.TryGetPropertyValue(prop, out value))
{
changed.TrySetPropertyValue(prop, value);
}
}
return changed;
}
/// <summary>
/// Exclude changes from a <see cref="Delta{T}"/> where the properties are marked with <see cref="NonEditableAttribute"/>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="delta"></param>
/// <returns></returns>
public static Delta<T> ExcludeNonEditable<T>(this Delta<T> delta)
where T : class
{
var excluded = typeof(T).NonEditableProperties();
return delta.Exclude(excluded);
}
}
And a domain class
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
[NonEditable]
public string SecurityId { get; set; }
}
Finally your controller can then take advantage of this in the Patch method
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var patch = delta.ExcludeNonEditable();
// TODO: Your patching action here
}
or
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Customer> delta)
{
var nonEditable = delta.NonEditableChanges();
if (nonEditable.Count > 0)
{
throw new HttpException(409, "Cannot update as non-editable fields included");
}
// TODO: Your patching action here
}
I had the same need and I ended up writing an extension method to Delta that accepts additional parameters to limit which fields to update (similar to TryUpDateModel)
I know there must be a better way to do this, but for now this works.
I had to recreate some of the Delta private methods and classes. Most of the code is from https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/Delta.cs, https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/OData/src/System.Web.Http.OData/OData/PropertyAccessor.cs and https://github.com/mono/aspnetwebstack/blob/master/src/System.Web.Http.OData/OData/CompiledPropertyAccessor.cs (or similar, these are not the exact url's I copied from)
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Linq.Expressions;
namespace MyProject.ODataExtensions
{
public static class ODataExtensions
{
public static void Patch<TEntityType>(this System.Web.OData.Delta<TEntityType> d, TEntityType original, String[] includedProperties, String[] excludedProperties) where TEntityType : class
{
Dictionary<string, PropertyAccessor<TEntityType>> _propertiesThatExist = InitializePropertiesThatExist<TEntityType>();
var changedProperties = d.GetChangedPropertyNames();
if (includedProperties != null) changedProperties = changedProperties.Intersect(includedProperties);
if (excludedProperties != null) changedProperties = changedProperties.Except(excludedProperties);
PropertyAccessor<TEntityType>[] array = (
from s in changedProperties
select _propertiesThatExist[s]).ToArray();
var array2 = array;
for (int i = 0; i < array2.Length; i++)
{
PropertyAccessor<TEntityType> propertyAccessor = array2[i];
propertyAccessor.Copy(d.GetEntity(), original);
}
}
private static Dictionary<string, PropertyAccessor<T>> InitializePropertiesThatExist<T>() where T : class
{
Type backingType = typeof(T);
return backingType.GetProperties()
.Where(p => p.GetSetMethod() != null && p.GetGetMethod() != null)
.Select<PropertyInfo, PropertyAccessor<T>>(p => new CompiledPropertyAccessor<T>(p))
.ToDictionary(p => p.Property.Name);
}
internal abstract class PropertyAccessor<TEntityType> where TEntityType : class
{
protected PropertyAccessor(PropertyInfo property)
{
if (property == null)
{
throw new System.ArgumentException("Property cannot be null","property");
}
Property = property;
if (Property.GetGetMethod() == null || Property.GetSetMethod() == null)
{
throw new System.ArgumentException("Property must have public setter and getter", "property");
}
}
public PropertyInfo Property
{
get;
private set;
}
public void Copy(TEntityType from, TEntityType to)
{
if (from == null)
{
throw new System.ArgumentException("Argument cannot be null", "from");
}
if (to == null)
{
throw new System.ArgumentException("Argument cannot be null", "to");
}
SetValue(to, GetValue(from));
}
public abstract object GetValue(TEntityType entity);
public abstract void SetValue(TEntityType entity, object value);
}
internal class CompiledPropertyAccessor<TEntityType> : PropertyAccessor<TEntityType> where TEntityType : class
{
private Action<TEntityType, object> _setter;
private Func<TEntityType, object> _getter;
public CompiledPropertyAccessor(PropertyInfo property)
: base(property)
{
_setter = MakeSetter(Property);
_getter = MakeGetter(Property);
}
public override object GetValue(TEntityType entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
return _getter(entity);
}
public override void SetValue(TEntityType entity, object value)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
_setter(entity, value);
}
private static Action<TEntityType, object> MakeSetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
ParameterExpression objectParameter = Expression.Parameter(typeof(Object));
MemberExpression toProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression fromValue = Expression.Convert(objectParameter, property.PropertyType);
BinaryExpression assignment = Expression.Assign(toProperty, fromValue);
Expression<Action<TEntityType, object>> lambda = Expression.Lambda<Action<TEntityType, object>>(assignment, entityParameter, objectParameter);
return lambda.Compile();
}
private static Func<TEntityType, object> MakeGetter(PropertyInfo property)
{
Type type = typeof(TEntityType);
ParameterExpression entityParameter = Expression.Parameter(type);
MemberExpression fromProperty = Expression.Property(Expression.TypeAs(entityParameter, property.DeclaringType), property);
UnaryExpression convert = Expression.Convert(fromProperty, typeof(Object));
Expression<Func<TEntityType, object>> lambda = Expression.Lambda<Func<TEntityType, object>>(convert, entityParameter);
return lambda.Compile();
}
}
}
}

C# IsEqual with ignorable list

I have some classes with lots of simple properties (from a datamodel I have no control over) -- I'd like to be able to find if the new version of an object is the same as an old version, but don't want to do 20 different "IsEqual" methods (I don't really like the "IsEqual" name because it is not an analog to ==). Another wrinkle, in most of the cases I don't want it to do a deep compare, but in some cases I do want that.
I'd like something along the lines of:
//Property could be PropertyInfo if that is necessary
bool IsEqual<T>(T first, T second, List<Property> ignorableProperties=emptyList, bool recurse=false)
{
//the comparison code returning if they are equal ignoring
//the properties in the ignorableProperties list, recursing if recurse == true
//not sure how I'd handle the comparison of sub-objects in the recursive step.
}
public static bool AreEqual<T>(this T first, T second,
bool recurse = false, params string[] propertiesToSkip)
{
if (Equals(first, second)) return true;
if (first == null)
return second == null;
else if (second == null)
return false;
if (propertiesToSkip == null) propertiesToSkip = new string[] { };
var properties = from t in first.GetType().GetProperties()
where t.CanRead
select t;
foreach (var property in properties)
{
if (propertiesToSkip.Contains(property.Name)) continue;
var v1 = property.GetValue(first, null);
var v2 = property.GetValue(second, null);
if (recurse)
if (!AreEqual(v1, v2, true, propertiesToSkip))
return false;
else
continue;
if (!Equals(v1, v2)) return false;
}
return true;
}
Here's something along those lines from our code base. It uses a list of properties to compare, rather than a list of properties to ignore. Then it returns a list of which properties did not match:
public static List<PropertyInfo> CompareObjects<T>(T o1, T o2, List<PropertyInfo> props) where T : class
{
var type = typeof(T);
var mismatched = CompareObjects(type, o1, o2, props);
return mismatched;
}
public static List<PropertyInfo> CompareObjects(Type t, object o1, object o2, List<PropertyInfo> props)
{
List<PropertyInfo> mismatched = null;
foreach (PropertyInfo prop in props)
{
if (prop.GetValue(o1, null) == null && prop.GetValue(o2, null) == null) ;
else if (
prop.GetValue(o1, null) == null || prop.GetValue(o2, null) == null ||
!prop.GetValue(o1, null).Equals(prop.GetValue(o2, null)))
{
if (mismatched == null) mismatched = new List<PropertyInfo>();
mismatched.Add(prop);
}
}
return mismatched;
}
If you wanted an IsEqual method it would be a matter of returning true/false when you find mismatched properties instead.
Hope this helps!
Here's how we achieve this in the Umbraco Framework, with a base class called AbstractEquatableObject which is a modified version of Sharp Architecture's BaseObject http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fAbstractEquatableObject.cs
Implementors override GetMembersForEqualityComparison() and the base class caches the PropertyInfo objects once per Type for the application in a ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>.
I've pasted the class here, although it refers to LogHelper elsewhere in the Framework so you can remove that (or just use our Framework lib, there's other useful stuff in there).
If you want a helper for getting a PropertyInfo from an expression, to avoid magic strings all over the place (e.g. replaced with x => x.MyProperty), check out the GetPropertyInfo methods of our ExpressionHelper at http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fExpressionHelper.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Framework.Diagnostics;
namespace Umbraco.Framework
{
/// <summary>
/// Objects implementing <see cref="AbstractEquatableObject{T}"/> are provided with common functionality for establishing domain-specific equality
/// and a robust implementation of GetHashCode
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public abstract class AbstractEquatableObject<T> where T : AbstractEquatableObject<T>
{
/// <summary>
/// Returns the real type in case the <see cref="object.GetType" /> method has been proxied.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual Type GetNativeType()
{
// Returns the real type in case the GetType method has been proxied
// See http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a for clarification
return GetType();
}
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. </returns>
public override int GetHashCode()
{
unchecked
{
// Based on an algorithm set out at http://sharp-architecture.googlecode.com/svn/trunk/src/SharpArch/SharpArch.Core/DomainModel/BaseObject.cs
var naturalIdMembers = EnsureEqualityComparisonMembersCached();
// It's possible for two objects to return the same hash code based on
// identically valued properties, even if they're of two different types,
// so we include the object's type in the hash calculation
var hashCode = GetType().GetHashCode();
if (!naturalIdMembers.Any()) return base.GetHashCode();
foreach (var value in naturalIdMembers
.Select(x => x.GetValue(this, null))
.Where(x => !ReferenceEquals(x, null)))
{
// Check if the property value is null or default (e.g. Guid.Empty)
// In which case we just want to use the base GetHashCode because we have no other way
// of determining if the instances are different
if (value.Equals(value.GetType().GetDefaultValue()))
hashCode = (hashCode * 41) ^ base.GetHashCode();
else
hashCode = (hashCode * 41) ^ value.GetHashCode();
}
return hashCode;
}
}
/// <summary>Determines whether the specified object is equal to this instance.</summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(obj, null)) return false;
var incoming = obj as AbstractEquatableObject<T>;
if (ReferenceEquals(incoming, null)) return false;
if (ReferenceEquals(this, incoming)) return true;
// (APN Oct 2011) Disabled the additional check for GetNativeType().Equals(incoming.GetNativeType())
// so that we can compare RelationById with Relation using Equals however this may need reinstating
// and using IComparable instead
return CompareCustomEqualityMembers(incoming);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
/// <remarks></remarks>
public static bool operator ==(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
{
// If both are null, or both are same instance, return true.
if (ReferenceEquals(left, right)) return true;
// If one is null, but not both, return false.
if (((object)left == null) || ((object)right == null)) return false;
return left.Equals(right);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
/// <remarks></remarks>
public static bool operator !=(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
{
return !(left == right);
}
/// <summary>
/// A static <see cref="ConcurrentDictionary{Type, IEnumerable{PropertyInfo}}"/> cache of natural ids for types which may implement this abstract class.
/// </summary>
protected readonly static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> EqualityComparisonMemberCache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
/// <summary>
/// Gets the natural id members.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected abstract IEnumerable<PropertyInfo> GetMembersForEqualityComparison();
/// <summary>
/// Ensures the natural id members are cached in the static <see cref="EqualityComparisonMemberCache"/>.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual IEnumerable<PropertyInfo> EnsureEqualityComparisonMembersCached()
{
return EqualityComparisonMemberCache.GetOrAdd(GetNativeType(), x => GetMembersForEqualityComparison());
}
/// <summary>
/// Establishes if the natural id of this instance matches that of <paramref name="compareWith"/>
/// </summary>
/// <param name="compareWith">The instance with which to compare.</param>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual bool CompareCustomEqualityMembers(AbstractEquatableObject<T> compareWith)
{
// Standard input checks - if it's the same instance, or incoming is null, etc.
if (ReferenceEquals(this, compareWith)) return true;
if (ReferenceEquals(compareWith, null)) return false;
// Get the natural id spec
var naturalIdMembers = EnsureEqualityComparisonMembersCached();
// If the overriding objct hasn't specified a natural id, just return the base Equals implementation
if (!naturalIdMembers.Any()) return base.Equals(compareWith);
// We have a natural id specified, so compare the members
foreach (var naturalIdMember in naturalIdMembers)
{
try
{
// Get the property values of this instance and the incoming instance
var localValue = naturalIdMember.GetValue(this, null);
var incomingValue = naturalIdMember.GetValue(compareWith, null);
// If the property values refere to the same instance, or both refer to null, continue the loop
if (ReferenceEquals(localValue, incomingValue) || (ReferenceEquals(localValue, null) && ReferenceEquals(incomingValue, null)))
continue;
// If this property value doesn't equal the incoming value, the comparison fails so we can return straight away
if (!localValue.Equals(incomingValue)) return false;
}
catch (Exception ex)
{
// If there was an error accessing one of the properties, log it and return false
LogHelper.TraceIfEnabled<AbstractEquatableObject<T>>("Error comparing {0} to {1}: {2}",
() => GetNativeType().Name,
() => compareWith.GetNativeType().Name,
() => ex.Message);
return false;
}
}
// To get this far means we haven't had any misses, so return true
return true;
}
}
}

Categories