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

Compare Properties of classes to identify which have changed

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

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; }
    }

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

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

P.S:上面的代码显示了我的项目class的一个非常简单的版本,实际class有30多个属性。所以我在想,与其对每个人进行比较 属性,不如有一种更简单、通用的方法来告诉我哪些属性发生了变化,这样我就可以根据它们进行通知。

看看PropertyChangedEventHandler。如果我正确理解您的问题,我认为它应该可以解决问题。 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

我假设您指的是 属性 值而不是实际的 class' 属性。为了能够比较哪些 属性 值发生了变化,必须有两个版本的对象,比如 oldupdated。我建议实现 IEquatable 接口,如果你有复杂的对象,在你的情况下嵌套的 class TaskStatus 也有你需要比较的自己的属性,这会很方便。您还可以让 TaskStatus 或其他嵌套的 classes 实现 IEquatable 接口,这样您就不必担心比较它们的 属性 值,从而获得以下优势对 Project's Equals() 方法进行一次调用。您可以在覆盖的 Equals() 方法中获取更改的逻辑。

如果您不想对每个 属性 进行硬编码以进行比较,稍微反思一下就可以了。 :)

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();
    }
}

基于反射的简单解决方案。请注意,它可以被优化,并且它不支持(此时)比较内部 collections/objects。比较对象必须是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);
    }
}

现在,您需要一个 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();
    }
}

然后...

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);
}

您可以按如下方式使用 class,它会在每次 属性 更改时存储 属性 的旧值和新值,即使在 UI 上更新它之后=] 然后该对象可用于检索这两个值。您需要做的就是在每个 属性 的 set 方法下创建此 class 的实例。您还可以在创建对象之前检查该值是否已更改。

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; }
}

在属性这边,你可以这样做:

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");
            }
        }
    }

更新后,您可以检查哪些属性已更改(或者您可以在每次更改 属性 时继续填充已更改属性的列表)并用旧值和新值邮寄该列表。