I am trying to write an extension method that compares objects based on their fields.
I have this:
public static class MyExtensions
{
public static bool FieldsEquals(this object o, object other)
{
if (ReferenceEquals(o, other))
return true;
if (o == null || other == null || o.GetType() != other.GetType())
return false;
foreach (var f in o.GetType().GetFields())
{
// is this a correct test ???
bool isEnumerable = f.FieldType != typeof(string) &&
typeof(IEnumerable).IsAssignableFrom(f.FieldType);
if (!isEnumerable)
{
if (!f.GetValue(o).Equals(f.GetValue(other)))
return false;
}
else
{
// convert both to IEnumerable and check if equal
}
}
return true;
}
}
I am struggling with the case of the field being a collection; I need to detect that case and then check the collections are the same (same number of elements and f.GetValue(o)[i] == f.GetValue(other)[i].
Any help?
Okay, like others already mentionend. There are a lot of edge cases..
I would recommend to use recursion for this kind of problem.
This method should also check arrays or lists that contain objects:
public static bool FieldsEquals(this object o1, object o2)
{
if (ReferenceEquals(o1, o2))
return true;
if (o1 == null || o2 == null || o1.GetType() != o2.GetType())
return false;
if (o1 is IEnumerable enumerable1 && o2 is IEnumerable enumerable2)
{
var enumerator1 = enumerable1.GetEnumerator();
var enumerator2 = enumerable2.GetEnumerator();
while(enumerator1.MoveNext())
{
if (!enumerator2.MoveNext())
{
return false;
}
if (!enumerator1.Current.FieldsEquals(enumerator2.Current))
{
return false;
}
}
}
else
{
foreach (var f in o1.GetType().GetFields())
{
var val1 = f.GetValue(o1);
var val2 = f.GetValue(o2);
if (val1 == null || val2 == null) continue;
if (val1 is IEnumerable e1 && val2 is IEnumerable e2)
{
if (!e1.FieldsEquals(e2))
{
return false;
}
}
else
{
if (!val1.Equals(val2))
{
return false;
}
}
}
}
return true;
}
To be honest, there are a lot of problems here. What if the types are basic types (like int) or Structs (datetime,etc). What if the enumerable fields contain classes not basic types? or the properties are classes?
See this question for some guidelines on deep equality: Compare the content of two objects for equality
All that said, here is my crack at the code you mentioned
public static bool FieldsEquals(this object o, object other)
{
if (ReferenceEquals(o, other)) return true;
if (o == null || other == null || o.GetType() != other.GetType()) return false;
foreach (var f in o.GetType().GetFields())
{
bool isEnumerable = f.GetValue(o).GetType().IsAssignableFrom(typeof(System.Collections.IEnumerable));// but is not a string
if (!isEnumerable)
{
if (!f.GetValue(o).Equals(f.GetValue(other))) return false;
}
else
{
var first = ((System.Collections.IEnumerable)f.GetValue(o)).Cast<object>().ToArray();
var second = ((System.Collections.IEnumerable)f.GetValue(other)).Cast<object>().ToArray();
if (first.Length != second.Length)
return false;
for (int i = 0; i < first.Length; i++)
{
if (first[i] != second[i]) //assumes they are basic types, which implement equality checking. If they are classes, you may need to recursively call this method
return false;
}
}
}
return true;
}
public static trnUser GetUserByEmail(string email)
{
if (string.IsNullOrEmpty(email) == true)
{
return null;
}
}
if (string.IsNullOrEmpty(email) == true) {
return null;
} else {
return new trnUser();
}
you need to provide information on what to return when the if statement is false.
I have these two functions, used to search for a folder within a hierarchy:
public Folder<T> Find (string Path)
{
Folder<T> Result = null;
if (this.Path != Path &&
ChildrenDict != null)
{
foreach (KeyValuePair<long, Folder<T>> Child in ChildrenDict)
{
Result = Child.Value.Find (Path);
}
}
else
{
Result = this;
}
return Result;
}
public Folder<T> Find (long ID)
{
Folder<T> Result = null;
if (this.ID != ID &&
ChildrenDict != null)
{
foreach (KeyValuePair<long, Folder<T>> Child in ChildrenDict)
{
Result = Child.Value.Find (ID);
}
}
else
{
Result = this;
}
return Result;
}
As you can see, they are very similar to one another. How can I re-structure them so I don't have essentially the same code several times, one per each property I might want to use to find them?
Create a method with a condition parameter which does the logic:
protected Folder<T> Find(Func<Folder<T>, bool> condition) {
Folder<T> Result = null;
if(!condition(this) && ChildrenDict != null) {
foreach(var Child in ChildrenDict) {
Result = Child.Value.Find(condition);
}
} else {
Result = this;
}
return Result;
}
Rewrite your public Find methods as:
public Folder<T> Find(string path) {
return Find(f => f.Path == path);
}
public static bool PropertiesEqual<T>(this T self, T other, string[] skip)
{
if (self.Equals(other)) return true;
var primitive = (from p in typeof(T).GetProperties()
where !skip.Contains(p.Name)
&& p.PropertyType.IsSimpleType()
select p).ToList();
var rest = (from p in typeof(T).GetProperties()
where !p.PropertyType.IsSimpleType() select p).ToList();
foreach(var pi in rest)
{
var selfValue = pi.GetValue(self, null);
var otherValue = pi.GetValue(other, null);
//var result = selfValue.PropertiesEqual(otherValue);
if (!object.Equals(selfValue, otherValue))
return false;
}
foreach (var pi in primitive)
{
var selfValue = pi.GetValue(self, null);
var otherValue = pi.GetValue(other, null);
return object.Equals(selfValue, otherValue);
}
return true;
}
public static bool IsSimpleType(this Type type)
{
return (type.IsValueType || type.IsPrimitive ||
type == typeof(String) || Convert.GetTypeCode(type) != TypeCode.Object);
}
I'm using this method to compare equality on my entity instances. On the first level it works great, but I would like to iterate over rest (attached entities) and do a recursive call to this method (the comment line).
The problem seems to be that self and other gets typed in to object on the recursive call, hence primitive gets zero results. If I inspect the type on self and this on the first level, I get the actual type, but on the second level I get object. I've tried using the Convert.ChangeType but it didn't help.
There's no reason to have it generic. Instead of typeof(T) use self.GetType() / other.GetType() to retrieve the correct run-time type.
What happens if you change the self and other value assignments to this?
var selfValue = Convert.ChangeType(pi.GetValue(self, null), pi.PropertyType);
var otherValue = Convert.ChangeType(pi.GetValue(other, null), pi.PropertyType);
I noticed that although T becomes object, self and other are of the correct type. So instead of gettign yoru propertes from typeof(T), try this:
var rest = (from p in self.GetType().GetProperties() select p).ToList();
In my simple test it worked and returned the wanted props.
You might have to change more than this one line :)
Way back, I did a comparerhelper and it allowed exclude properties.
Maybe code can help you:
public class CompareHelper
{
Hashtable reccorido = new Hashtable();
List<IExcludeProperties> excludeProperties = new List<IExcludeProperties>();
private readonly List<string> genericListPropertiesNames = new List<string>() { "Count", "Capacity", "Item" };
public CompareHelper():this(new List<IExcludeProperties>())
{
}
public CompareHelper(List<IExcludeProperties> excludeProperties)
{
this.excludeProperties = excludeProperties;
}
public bool AreEquals<T1, T2>(T1 value1, T2 value2)
{
try
{
reccorido = new Hashtable();
return Compare(value1, value2);
}
catch (NotEqualsException ex)
{
PropertyFail = ex.Where();
return false;
}
}
public string PropertyFail
{
get;
private set;
}
private bool Compare<T1, T2>(T1 value1, T2 value2)
{
if (value1 == null && value2 == null)
{
return true;
}
if ((value1 == null) || (value2 == null))
{
throw new NotEqualsException(value1, value2);
//return false;
}
string key = GetKey<T1, T2>(value1, value2);
if (reccorido.Contains(key))
{
return true;
}
reccorido.Add(key, true);
Type tipo1 = GetType(value1.GetType());
Type tipo2 = GetType(value2.GetType());
if (tipo1 != tipo2)
{
throw new NotEqualsException(value1, value2);
// return false;
}
if (IsBasicCompare(tipo1))
{
return CompareBasic(ConvertTo(value1, tipo1), ConvertTo(value2, tipo1));
}
dynamic v1 = ConvertTo(value1, tipo1);
dynamic v2 = ConvertTo(value2, tipo1);
if (!CompareFields(v1, v2))
{
throw new NotEqualsException(value1, value2);
//return false;
}
return CompareProperties(v1, v2);
}
private string GetKey<T1, T2>(T1 value1, T2 value2)
{
int hascodeA = value1.GetHashCode();
int hascodeB = value2.GetHashCode();
if (hascodeA > hascodeB)
return string.Format("{0}{1}", hascodeA, hascodeB);
return string.Format("{0}{1}", hascodeB, hascodeA);
}
private dynamic ConvertTo(object value1, Type t)
{
if (value1 == null)
return null;
return Convert.ChangeType(value1, GetType(t));
}
private bool CompareProperties<T>(T value1, T value2)
{
if (IsGenericList(typeof(T)))
{
return ComparareGenericList(value1, value2);
}
List<PropertyInfo> properties = GetPropertiesToCheck<T>();
foreach (var p in properties)
{
try
{
var valueA = p.GetValue(value1, null);
var valueB = p.GetValue(value2, null);
if (!(valueA == null && valueB == null))
{
if (valueA == null || valueB == null)
{
throw new NotEqualsException(value1, value2);
// return false;
}
if (IsBasicCompare(p.PropertyType))
{
valueA = ConvertTo(p.GetValue(value1, null), p.PropertyType);
valueB = ConvertTo(p.GetValue(value2, null), p.PropertyType);
if (!CompareBasic(valueA, valueB))
{
throw new NotEqualsException(value1, value2);
// return false;
}
}
else if (IsEnumerable(p.PropertyType))
{
if (!CompareAsInumerable(valueA, valueB))
{
throw new NotEqualsException(value1, value2);
// return false;
}
}
else if (p.PropertyType.IsClass)
{
if (!Compare(ConvertTo(p.GetValue(value1, null), p.PropertyType), ConvertTo(p.GetValue(value2, null), p.PropertyType)))
{
throw new NotEqualsException(value1, value2);
}
}
else
throw new Exception(string.Format("Tipo no especificado {0}", p.PropertyType));
}
}
catch (NotEqualsException ex)
{
ex.AddParent(p.Name);
throw;
}
}
return true;
}
private List<PropertyInfo> GetPropertiesToCheck<T>()
{
List<PropertyInfo> properties = new List<PropertyInfo>();
Type typeToCheck= typeof(T);
IExcludeProperties exclude=excludeProperties.FirstOrDefault(excl=>excl.ExcludeType().IsAssignableFrom(typeToCheck));
if(exclude!=null)
return typeToCheck.GetProperties().Where(p => p.CanRead && (!exclude.GetPropertiesNames().Any(n=>n==p.Name))).ToList();
//
return typeToCheck.GetProperties().Where(p => p.CanRead).ToList();
}
private bool ComparareGenericList<T>(T value1, T value2)
{
List<PropertyInfo> properties = typeof(T).GetProperties().Where(p => p.CanRead && p.Name != "Capacity").ToList(); //la capacidad no la compruebo!!
PropertyInfo count = typeof(T).GetProperty("Count");
int totalA = ConvertTo(count.GetValue(value1, null), count.PropertyType);
int totalB = ConvertTo(count.GetValue(value2, null), count.PropertyType);
if (!Compare(totalA, totalB))
return false;
PropertyInfo item = typeof(T).GetProperty("Item");
CompareAsInumerable(value1, value2);
return true;
}
private bool IsGenericList(Type t)
{
return t.IsGenericType && IsEnumerable(t) && t.GetProperties().Where(p => p.CanRead).Any(p => genericListPropertiesNames.Contains(p.Name));
}
[Conditional("DEBUG")]
private void ShowInfo(PropertyInfo p)
{
Debug.WriteLine(string.Format("Checkeando propiedad {0}",p.Name));
}
private bool CompareFields<T>(T value1, T value2)
{
List<FieldInfo> fields = typeof(T).GetFields().Where(f => f.IsPublic).ToList();
foreach (var f in fields)
{
dynamic valueA = f.GetValue(value1);
dynamic valueB = f.GetValue(value2);
if (!Compare(f.GetValue(value1), f.GetValue(value2)))
{
throw new NotEqualsException(value1, value2);
//return false;
}
}
return true;
}
private bool CompareAsInumerable<T>(T valueA, T valueB)
{
IEnumerable<object> colA = ((IEnumerable)valueA).Cast<object>();
IEnumerable<object> colB = ((IEnumerable)valueB).Cast<object>();
if (colA.Count() != colB.Count())
return false;
Type t1 = GetType(colA.GetType());
Type t2 = GetType(colB.GetType());
if (t1 != t2)
return false;
if (colA.Count() > 0)
{
Type itemType = GetTypeOfItem(colA);
for (int i = 0; i < colA.Count(); i++)
{
try
{
dynamic a = colA.ElementAt(i);
dynamic b = colB.ElementAt(i);
if (!Compare(a, b))
{
throw new NotEqualsException(colA.ElementAt(i), colB.ElementAt(i));
//return false;
}
}
catch (NotEqualsException ex)
{
ex.AddParent(itemType.Name);
throw ;
}
}
}
return true;
}
private Type GetTypeOfItem(IEnumerable<object> collection)
{
if (collection == null)
return null;
Type[] t = collection.GetType().GetGenericArguments();
if ((t != null) && (t.Count() > 0))
return t[0];
return null;
}
private bool IsEnumerable(Type type)
{
return typeof(IEnumerable).IsAssignableFrom(type);
}
private bool CompareBasic<T>(T valueA, T valueB)
{
bool result;
IComparable selfValueComparer;
selfValueComparer = valueA as IComparable;
if (valueA == null && valueB != null || valueA != null && valueB == null)
result = false;
else if (selfValueComparer != null && selfValueComparer.CompareTo(valueB) != 0)
result = false;
else if (!object.Equals(valueA, valueB))
result = false;
else
result = true;
if (!result)
throw new NotEqualsException(valueA, valueB);
return result;
}
private bool IsBasicCompare(Type type)
{
return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
private Type GetType<T>()
{
return GetType(typeof(T));
}
private Type GetType(Type t)
{
Type tipo = Nullable.GetUnderlyingType(t);
if (tipo == null)
tipo = t;
return (tipo == null) ? t : tipo;
}
}
Helper class:
public interface IExcludeProperties
{
Type ExcludeType();
void AddPropertyName(string propertyName);
List<string> GetPropertiesNames();
}
public class ExcludeProperties<T> : IExcludeProperties
{
HashSet<string> propertiesNames = new HashSet<string>();
List<PropertyInfo> props = new List<PropertyInfo>();
public ExcludeProperties()
{
props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly).ToList();
}
public Type ExcludeType()
{
return typeof(T);
}
public void AddPropertyName(string propertyName)
{
if(! typeof(T).IsAbstract && !props.Any(p=>p.Name==propertyName) )
throw new Exception(string.Format("No existe y no por lo tanto no se puede excluir la propiedad {0} para el tipo {1}!",propertyName,typeof(T).Name));
propertiesNames.Add(propertyName);
}
public List<string> GetPropertiesNames()
{
return propertiesNames.ToList();
}
}
Would some kind person help me sort out the output of .Net Reflector v6.5 that does not compile? I think that the symbols are out of whack but global search and replace might fix that. I don't get the odd class definition. Ideas?
[CompilerGenerated]
private sealed class <ApplicationTaskIterator>d__0 : IEnumerable<ApplicationTask>, IEnumerable, IEnumerator<ApplicationTask>, IEnumerator, IDisposable
{
private int <>1__state;
private ApplicationTask <>2__current;
public SessionMetrics <>3__sm;
public Dictionary<int, ThreadMetrics> <>7__wrap3;
public Dictionary<int, ThreadMetrics>.ValueCollection.Enumerator <>7__wrap4;
public ApplicationTask <currentTask>5__1;
public ThreadMetrics <tm>5__2;
public SessionMetrics sm;
[DebuggerHidden]
public <ApplicationTaskIterator>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
}
private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
Monitor.Enter(this.<>7__wrap3 = ThreadMetrics._allThreadMetrics);
this.<>1__state = 1;
this.<>7__wrap4 = ThreadMetrics._allThreadMetrics.Values.GetEnumerator();
this.<>1__state = 2;
while (this.<>7__wrap4.MoveNext())
{
this.<tm>5__2 = this.<>7__wrap4.Current;
if ((((this.<tm>5__2._managedThread.ThreadState == System.Threading.ThreadState.Stopped) || object.ReferenceEquals(this.<tm>5__2._managedThread, Thread.CurrentThread)) || ((this.<currentTask>5__1 = this.<tm>5__2.CurrentApplicationTask) == null)) || ((this.sm != null) && !this.<currentTask>5__1.CurrentSessionMetrics.SessionGUID.Equals(this.sm.SessionGUID)))
{
continue;
}
this.<currentTask>5__1.Active = !this.<tm>5__2.Suspended;
this.<>2__current = this.<currentTask>5__1;
this.<>1__state = 3;
return true;
Label_010C:
this.<>1__state = 2;
}
this.<>1__state = 1;
this.<>7__wrap4.Dispose();
this.<>1__state = -1;
Monitor.Exit(this.<>7__wrap3);
break;
case 3:
goto Label_010C;
}
return false;
}
fault
{
((IDisposable) this).Dispose();
}
}
}
Used like this:
internal static IEnumerable<ApplicationTask> ApplicationTaskIterator(SessionMetrics sm)
{
return new <ApplicationTaskIterator>d__0(-2) { <>3__sm = sm };
}
That's simply what the C# compiler transforms a method containing yield return statements to.
If you want to make the code compile, you can either try to decipher what the method is doing and recreate the original version with yield return statements; or you can rename the class and all members to valid C# names.
The original method probably looked like this:
internal static IEnumerable<ApplicationTask> ApplicationTaskIterator(SessionMetrics sm)
{
lock (ThreadMetrics._allThreadMetrics)
{
foreach (var tm in ThreadMetrics._allThreadMetrics.Values)
{
if (tm._managedThread.ThreadState != ThreadState.Stopped)
{
if (!object.ReferenceEquals(tm._managedThread, Thread.CurrentThread))
{
ApplicationTask currentTask;
if ((currentTask = tm.CurrentApplicationTask) != null)
{
if (sm == null || !currentTask.CurrentSessionMetrics.SessionGUID.Equals(sm.SessionGUID))
{
currentTask.Active = !tm.Suspended;
yield return currentTask;
}
}
}
}
}
}
}