Search code examples
c#genericswindows-store-appsgeneric-listgeneric-programming

Calculate changeset for object


I'm writing a windows store app that receives an object (custom type that I created) from a server, lets the user edit the object and then submits a "changeset" back to the server. This changeset is simply an object of the same type as the received object with every field set to null except the fields the user edited. Those fields then contain the user's edits like so:

Original Case:
    Description: "Cracked exhaust pipe"
    Status: "Open"
    Customer: ""
    Mileage: 10000

Edited Case:
    Description: ""
    Status "Open"
    Customer: "Example inc."
    Mileage: 10000

Case changeset:
    Description: ""
    Status: null
    Customer: "Example inc."
    Mileage: null

When the app first downloads the object from the server I make a copy of it for later comparison. The user then makes changes to one of the objects and when the user submits his changes, a changeset of these two objects is calculated with a generic method CalculateChangeSet. It goes through every property of the two objects and compares them for equality:

public static T CalculateChangeSet<T>(T oldObject, T newObject) where T : new()
{
    T changeSet = (T)Activator.CreateInstance(oldObject.GetType());

    foreach (PropertyInfo property in oldObject.GetType().GetRuntimeProperties())
    {
        var oldValue = property.GetValue(oldObject);
        var newValue = newObject.GetType().GetRuntimeProperty(property.Name).GetValue(newObject);
        if (oldValue != null && newValue != null)
        {
            if (oldValue is IList)
            {
                Type listType = oldValue.GetType().GetRuntimeProperty("Item").PropertyType;

                IList<listType> oldList = (IList<listType>)oldValue; //Visual studio complains about
                IList<listType> newList = (IList<listType>)newValue; //listType not being found

                if (!oldList.SequenceEqual(newList))
                {
                    changeSet.GetType().GetRuntimeProperty(property.Name).SetValue(changeSet, newValue, null);
                }
            }
            else
            {
                if (!oldValue.Equals(newValue))
                {
                    changeSet.GetType().GetRuntimeProperty(property.Name).SetValue(changeSet, CalculateChangeSet(oldValue, newValue));
                }
            }
        }
        else
        {
            changeSet.GetType().GetRuntimeProperty(property.Name).SetValue(changeSet, newValue);
        }
    }

    return changeSet;
}

The method works fine for every property I've come across except for Lists so I created the if clause to deal with lists. Since list1.Equals(list2) does not compare items in the lists, we need to cast oldValue and newValue to List<T> to be able to use list.SequenceEqual().

Why do I get an error saying The type or namespace name 'listType' could not be found (are you missing a using directive or an assembly reference?) when I try to use it to create new lists? If there is a better way to approach this problem I'm open to suggestions..


Solution

  • Try this:

    IList oldList = (IList)oldValue; //Visual studio complains about
    IList newList = (IList)newValue; //listType not being found
    
    if (!NonGenericSequenceEqual(oldList, newList))
    {
        changeSet.GetType().GetRuntimeProperty(property.Name).SetValue(changeSet, newValue, null);
    }
    
    public static bool NonGenericSequenceEqual(IList a, IList b)
    {
        if (ReferenceEquals(a, b))
        {
            return true;
        }
        else if (a == null || b == null)
        {
            return false;
        }
        else
        {
            int count = a.Count;
    
            if (count != b.Count)
            {
                return false;
            }
            else
            {
                for (int i = 0; i < count; i++)
                {
                    if (!Object.Equals(a[i], b[i]))
                    {
                        return false;
                    }
                }
    
                return true;
            }
        }
    }
    

    Weak code is 'Object.Equals(a[i], b[i])'

    Another variant via reflection:

    if (!(bool)typeof(System.Linq.Enumerable).GetMethod("SequenceEqual").MakeGenericMethod(oldValue.GetType().GetRuntimeProperty("Item").PropertyType).Invoke(null, new object[] { oldObject, newObject }))
    {
        changeSet.GetType().GetRuntimeProperty(property.Name).SetValue(changeSet, newValue, null);
    }