简体   繁体   中英

Compare Properties of classes to identify which have changed

In my web application, I want to notify user when certain things are changed through UI. For example my Project class looks like this

public class Project
    {
        public string Name { get; set; }
        public TaskStatus Status { get; set; }
        public string Planner { get; set; }
        public DateTime ScheduleStart { get; set; }
        public DateTime ScheduleEnd { get; set; }
        public double EstimatedCost { get; set; }
        public double ActualCost { get; set; }
        public string AssignedTo { get; set; }
    }

Now I have this information shown up on UI and a particular user having rights to change certain things (eg Status, schedule, cost etc.) can change this information. So what I want is that when something is changed by a user, then Emails should be sent to notify Project Manager lets say or anyone interested.

I have all other required code written to send emails and manage rights etc. Now I want to specifically see exactly what things changed for example If only Planner changed, or status changed then email should contain new and old value like TFS generates notifications.

PS: Above code shows a very simple version of my Project class, actual class has more than 30 attributes. So I was thinking that instead of making comparison of each individual property there should be an easier and generic way that tells me which properties have changed, so that I can notify based on them.

A simple solution based on reflection. Note that it could be optimized, and it doesn't support (at this time) comparing inner collections/objects. The compared object must be POD (Plain Old Data)

public class Project
{
    public string Name { get; set; }
    public TaskStatus Status { get; set; }
    public string Planner { get; set; }
    public DateTime ScheduleStart { get; set; }
    public DateTime ScheduleEnd { get; set; }
    public double EstimatedCost { get; set; }
    public double ActualCost { get; set; }
    public string AssignedTo { get; set; }

    public Project Clone()
    {
        // If your object has inner collections, or
        // references to other objects, you'll have to deep
        // clone them ***manually***!!!
        return (Project)MemberwiseClone();
    }
}

public static class SimpleComparer
{
    // Item1: property name, Item2 current, Item3 original
    public static List<Tuple<string, object, object>> Differences<T>(T current, T original)
    {
        var diffs = new List<Tuple<string, object, object>>();

        MethodInfo areEqualMethod = typeof(SimpleComparer).GetMethod("AreEqual", BindingFlags.Static | BindingFlags.NonPublic);

        foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            object x = prop.GetValue(current);
            object y = prop.GetValue(original);
            bool areEqual = (bool)areEqualMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { x, y });

            if (!areEqual)
            {
                diffs.Add(Tuple.Create(prop.Name, x, y));
            }
        }

        return diffs;
    }

    private static bool AreEqual<T>(T x, T y)
    {
        return EqualityComparer<T>.Default.Equals(x, y);
    }
}

Now, you'll need a Clone() method:

public class Project
{
    public string Name { get; set; }
    public TaskStatus Status { get; set; }
    public string Planner { get; set; }
    public DateTime ScheduleStart { get; set; }
    public DateTime ScheduleEnd { get; set; }
    public double EstimatedCost { get; set; }
    public double ActualCost { get; set; }
    public string AssignedTo { get; set; }

    public Project Clone()
    {
        // If your object has inner collections, you'll have to deep
        // clone them ***manually***!!!
        return (Project)MemberwiseClone();
    }
}

and then...

var current = new Project();
var original = current.Clone();
current.ActualCost = 10000;

var diffs = SimpleComparer.Differences(current, original);

foreach (var diff in diffs)
{
    Console.WriteLine("'{0}' changed from {1} to {2}", diff.Item1, diff.Item3, diff.Item2);
}

I'm assuming that you are referring to property values and not the actual class' properties. To be able to compare which property values have changed, there has to be two versions of the object, say old and updated . I would suggest implementing the IEquatable interface, this comes handy if you have complex objects which in your case a nested class TaskStatus which also have properties of its own that you need to compare. You can also let TaskStatus or other nested classes implement the IEquatable interface such that you don't have to worry about comparing their property values giving you the advantage of just doing a single call to Project's Equals() method. You can have the logic for getting the changes inside the overriden Equals() method.

If you don't want to hardcode each property for comparison, a little reflection would do. :)

public class Project : IEquatable<Project>
{
    public string Name { get; set; }
    public TaskStatus Status { get; set; }
    public string Planner { get; set; }
    public DateTime ScheduleStart { get; set; }
    public DateTime ScheduleEnd { get; set; }
    public double EstimatedCost { get; set; }
    public double ActualCost { get; set; }
    public string AssignedTo { get; set; }

    public bool Equals(Project other)
    {
        bool flag = true;

        if (this.Name != other.Name)
            //--Do something
            flag = false;

        //TaskStatus otherTaskStatus = other.Status;
        //flag = other.Status.Equals(otherTaskStatus);//compare nested classes here

        return flag;
    }
}

public class TaskStatus : IEquatable<TaskStatus>
{
    public bool Equals(TaskStatus other)
    {
        throw new NotImplementedException();
    }
}

You can use a class as follows which will store the old and new values of a property each time the property is changed even after updating it on the UI and then the object can be used to retrieve both the values. All you need to do is to create an instance of this class under set method of each property. You can also check whether the value is changed or not before creating the object.

public class PropertyChangingEventArgs : EventArgs
{
    public PropertyChangingEventArgs()
    {
    }

    public PropertyChangingEventArgs(string propName, object oldValue, object newValue)
    {
        PropertyName = propName;
        OldValue = oldValue;
        NewValue = newValue;
    }

    public string PropertyName { get; set; }

    public object OldValue { get; set; }

    public object NewValue { get; set; }
}

On property side, you can do this:

private string family;
    public string Family
    {
        get { return family; }
        set
        {
            if (family != value)
            {
                PropertyChangingEventArgs e = new PropertyChangingEventArgs("Family", family, value);
                OnPropertyChanging(e);
                family = value;
                OnPropertyChanged("Family");
            }
        }
    }

After updating, you can check which all properties have changed (or you can keep populating a list of changed properties each time a property is changed) and mail the list with old and new values.

Take a look at PropertyChangedEventHandler . I think it should do the trick if I am understanding your question correctly. https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM