比较类的属性以确定哪些已更改

WAQ*_*WAQ 2 c# asp.net comparison

在我的 Web 应用程序中,我想在某些内容通过 UI 发生更改时通知用户。例如我的项目类看起来像这样

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; }
    }
Run Code Online (Sandbox Code Playgroud)

现在,我已将此信息显示在 UI 上,并且有权更改某些内容(例如状态、计划、成本等)的特定用户可以更改此信息。所以我想要的是,当用户更改某些内容时,应该发送电子邮件以通知项目经理或任何感兴趣的人。

我编写了所有其他所需的代码来发送电子邮件和管理权限等。现在我想具体了解哪些内容发生了变化,例如如果只有 Planner 发生了变化,或者状态发生了变化,那么电子邮件应该包含新值和旧值,就像 TFS 生成通知一样。

PS:上面的代码显示了我的 Project 类的一个非常简单的版本,实际的类有 30 多个属性。所以我在想,不应该对每个单独的属性进行比较,而应该有一种更简单和通用的方法来告诉我哪些属性发生了变化,以便我可以根据它们进行通知。

xan*_*tos 5

基于反射的简单解决方案。请注意,它可以进行优化,并且它不支持(目前)比较内部集合/对象。比较对象必须是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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要一个Clone()方法:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

进而...

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);
}
Run Code Online (Sandbox Code Playgroud)