比较 类 的属性以确定哪些已更改
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' 属性。为了能够比较哪些 属性 值发生了变化,必须有两个版本的对象,比如 old
和 updated
。我建议实现 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");
}
}
}
更新后,您可以检查哪些属性已更改(或者您可以在每次更改 属性 时继续填充已更改属性的列表)并用旧值和新值邮寄该列表。
在我的 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' 属性。为了能够比较哪些 属性 值发生了变化,必须有两个版本的对象,比如 old
和 updated
。我建议实现 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");
}
}
}
更新后,您可以检查哪些属性已更改(或者您可以在每次更改 属性 时继续填充已更改属性的列表)并用旧值和新值邮寄该列表。