c#将class属性标记为脏

以下是定义对象状态的枚举的简单示例,以及显示此枚举实现的类。

public enum StatusEnum { Clean = 0, Dirty = 1, New = 2, Deleted = 3, Purged = 4 } public class Example_Class { private StatusEnum _Status = StatusEnum.New; private long _ID; private string _Name; public StatusEnum Status { get { return _Status; } set { _Status = value; } } public long ID { get { return _ID; } set { _ID = value; } } public string Name { get { return _Name; } set { _Name = value; } } } 

当使用数据库中的数据填充类对象时,我们将枚举值设置为“clean”。 为了将大部分逻辑保留在表示层之外,如何在更改属性时将枚举值设置为“脏”。

我正在思考一些事情;

 public string Name { get { return _Name; } set { if (value != _Name) { _Name = value; _Status = StatusEnum.Dirty; } } } 

在该类的每个属性的setter中。

这听起来像个好主意,有没有人对如何在表示层中分配脏标志有更好的想法。

如果你确实想要在类级别(或者,就此而言,通知)脏标志 – 你可以使用如下的技巧来最小化你的属性中的混乱(这里显示IsDirtyPropertyChanged ,只是为了好玩)。

显然使用枚举方法是一件小事(我没有的唯一原因是保持示例简单):

 class SomeType : INotifyPropertyChanged { private int foo; public int Foo { get { return foo; } set { SetField(ref foo, value, "Foo"); } } private string bar; public string Bar { get { return bar; } set { SetField(ref bar, value, "Bar"); } } public bool IsDirty { get; private set; } public event PropertyChangedEventHandler PropertyChanged; protected void SetField(ref T field, T value, string propertyName) { if (!EqualityComparer.Default.Equals(field, value)) { field = value; IsDirty = true; OnPropertyChanged(propertyName); } } protected virtual void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

您也可以选择将其中的一部分推送到抽象基类中,但这是一个单独的讨论

一种选择是在写入时改变它; 另一种方法是保留所有原始值的副本,并在有人要求时计算肮脏。 这样做的另一个好处是,您可以确切地确定哪些字段已更改(以及以何种方式),这意味着您可以发出最小的更新语句并使合并冲突解决方案更容易一些。

您还可以将所有肮脏检查放在一个地方,这样就不会污染代码的其余部分。

我并不是说这是完美的,但这是一个值得考虑的选择。

如果要以这种方式实现它,并且希望减少代码量,可以考虑应用面向方面编程。

例如,您可以使用像PostSharp这样的编译时编织器,并创建可应用于属性的“方面”。 然后,此方面确保在适当时设置脏标志。

方面可以如下所示:

 [Serializable] [AttributeUsage(AttributeTargets.Property)] public class ChangeTrackingAttribute : OnMethodInvocationAspect { public override void OnInvocation( MethodInvocationEventArgs e ) { if( e.Delegate.Method.ReturnParameter.ParameterType == typeof(void) ) { // we're in the setter IChangeTrackable target = e.Delegate.Target as IChangeTrackable; // Implement some logic to retrieve the current value of // the property if( currentValue != e.GetArgumentArray()[0] ) { target.Status = Status.Dirty; } base.OnInvocation (e); } } } 

Offcourse,这意味着您要实现ChangeTracking的类应该实现IChangeTrackable接口(自定义接口),该接口至少具有“Status”属性。

您还可以创建自定义属性ChangeTrackingProperty ,并确保上面创建的方面仅应用于使用此ChangeTrackingProperty属性ChangeTrackingProperty属性。

例如:

 public class Customer : IChangeTrackable { public DirtyState Status { get; set; } [ChangeTrackingProperty] public string Name { get; set; } } 

这是我看到它的一点点。 您甚至可以确保PostSharp在编译时检查具有使用ChangeTrackingProperty属性修饰的属性的类是否实现了IChangeTrackable接口。

看看PostSharp( http://www.postsharp.org/ )。 您可以轻松创建一个将其标记为脏的属性,您可以将attrubute添加到需要它的每个属性,并将所有代码保存在一个位置。

粗略地说创建一个具有您的状态的接口使该类实现它。 创建一个属性,该属性可以应用于属性并强制转换为接口,以便在某些内容更改某个标记属性时设置该值。

此方法基于此线程中提供的一组不同概念。 我以为我会把它放在那里,正在寻找一种方法来干净利落地做到这一点,就像我自己一样。

这种混合概念的关键是:

  1. 您不希望复制数据以避免膨胀和资源占用;
  2. 您想知道对象的属性何时从给定的原始/清除状态更改;
  3. 您希望IsDirty标志既准确,又需要很少的处理时间/功率来返回值; 和
  4. 您希望能够告诉对象何时再次考虑自己清洁。 这在UI中构建/工作时尤其有用。

鉴于这些要求,这就是我提出的,它似乎对我来说非常有用,并且在处理UI并准确捕获用户更改时变得非常有用。 我还在下面发布了一个“如何使用”,向您展示我如何在UI中使用它。

物体

 public class MySmartObject { public string Name { get; set; } public int Number { get; set; } private int clean_hashcode { get; set; } public bool IsDirty { get { return !(this.clean_hashcode == this.GetHashCode()); } } public MySmartObject() { this.Name = ""; this.Number = -1; MakeMeClean(); } public MySmartObject(string name, int number) { this.Name = name; this.Number = number; MakeMeClean(); } public void MakeMeClean() { this.clean_hashcode = this.Name.GetHashCode() ^ this.Number.GetHashCode(); } public override int GetHashCode() { return this.Name.GetHashCode() ^ this.Number.GetHashCode(); } } 

它很简单并且满足了我们的所有要求:

  1. 对于脏检查,数据不重复…
  2. 这会考虑所有属性更改方案(请参阅下面的方案)……
  3. 当您调用IsDirty属性时,将执行一个非常简单且小的Equals操作,并且可以通过GetHashCode覆盖完全自定义…
  4. 通过调用MakeMeClean方法,您现在可以再次使用干净的对象!

当然你可以适应这种情况来包含一堆不同的状态……这真的取决于你。 此示例仅显示如何进行正确的IsDirty标志操作。

方案
让我们来看看这个的一些场景,看看会有什么回来:

  • 情景1
    使用空构造函数创建新对象,
    属性名称从“”更改为“James”,
    对IsDirty的调用返回True! 准确。

  • 场景2
    使用“John”和12345的参数创建新对象,
    属性名称从“John”更改为“James”,
    属性名称从“James”变回“John”,
    对IsDirty的调用返回False。 准确,我们也不必复制数据来做到这一点!

如何使用WinForms UI示例
这只是一个示例,您可以通过UI以多种不同方式使用它。

假设你有两种forms([A]和[B])。

第一个([A])是您的主要表单,第二个([B])是一个允许用户更改MySmartObject中的值的表单。

[A]和[B]forms都声明了以下属性:

 public MySmartObject UserKey { get; set; } 

当用户单击[A]表单上的按钮时,将创建[B]表单的实例,其属性已设置并显示为对话框。

表单[B]返回后,[A]表单根据[B]表单的IsDirty检查更新其属性。 像这样:

 private void btn_Expand_Click(object sender, EventArgs e) { SmartForm form = new SmartForm(); form.UserKey = this.UserKey; if(form.ShowDialog() == DialogResult.OK && form.UserKey.IsDirty) { this.UserKey = form.UserKey; //now that we have saved the "new" version, mark it as clean! this.UserKey.MakeMeClean(); } } 

此外,在[B]中,当它关闭时,您可以检查并提示用户是否正在关闭包含未保存更改的表单,如下所示:

  private void BForm_FormClosing(object sender, FormClosingEventArgs e) { //If the user is closing the form via another means than the OK button, or the Cancel button (eg: Top-Right-X, Alt+F4, etc). if (this.DialogResult != DialogResult.OK && this.DialogResult != DialogResult.Ignore) { //check if dirty first... if (this.UserKey.IsDirty) { if (MessageBox.Show("You have unsaved changes. Close and lose changes?", "Unsaved Changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) e.Cancel = true; } } } 

正如您从上面的示例中看到的,这可能是一个非常有用的东西,因为它真的简化了UI。

注意事项

  • 每次实现此操作时,都必须将其自定义为您正在使用的对象。 例如:没有使用reflection就没有“简单”的通用方法……如果你使用reflection,你会失去效率,特别是在大型和复杂的物体中。

希望这有助于某人。

你的方法基本上就是我的方法。 我只是删除Status属性的setter:

 public StatusEnum Status { get { return _Status; } // set { _Status = value; } } 

而是添加一个function

 public SetStatusClean() { _Status = StatusEnum.Clean; } 

以及SetStatusDeleted()SetStatusPurged() ,因为我发现它更好地表明了意图。

编辑

在阅读了Jon Skeet的回答之后 ,我需要重新考虑我的方法;-)对于简单的对象,我会坚持自己的方式,但如果它变得更复杂,他的提议将导致更好的组织代码。

如果您的Example_Class是轻量级的,请考虑存储原始状态,然后将当前状态与原始状态进行比较以确定更改。 如果不是你的方法是最好的,因为在这种情况下,stroing原始状态会消耗大量的系统资源。

除了“考虑让你的类型永不变化”的建议之外,这里有我写的东西(让Jon和Marc一路教我一些东西)

 public class Example_Class { // snip // all properties are public get and private set private Dictionary m_PropertySetterMap; public Example_Class() { m_PropertySetterMap = new Dictionary(); InitializeSettableProperties(); } public Example_Class(long id, string name):this() { this.ID = id; this.Name = name; } private void InitializeSettableProperties() { AddToPropertyMap("ID", value => { this.ID = value; }); AddToPropertyMap("Name", value => { this.Name = value; }); } // jump thru a hoop because it won't let me cast an anonymous method to an Action/Delegate private void AddToPropertyMap(string sPropertyName, Action setterAction) { m_PropertySetterMap.Add(sPropertyName, setterAction); } public void SetProperty(string propertyName, T value) { (m_PropertySetterMap[propertyName] as Action).Invoke(value); this.Status = StatusEnum.Dirty; } } 

你明白了……可能的改进:使用PropertyNames的常量并检查属性是否真的发生了变化。 这里的一个缺点是

 obj.SetProperty("ID", 700); // will blow up int instead of long obj.SetProperty("ID", 700); // be explicit or use 700L 

我是这样做的。

如果我不需要测试特定字段是否脏,我有一个抽象类:

 public abstract class SmartWrap : ISmartWrap { private int orig_hashcode { get; set; } private bool _isInterimDirty; public bool IsDirty { get { return !(this.orig_hashcode == this.GetClassHashCode()); } set { if (value) this.orig_hashcode = this.orig_hashcode ^ 108.GetHashCode(); else MakeClean(); } } public void MakeClean() { this.orig_hashcode = GetClassHashCode(); this._isInterimDirty = false; } // must be overridden to return combined hashcodes of fields testing for // example Field1.GetHashCode() ^ Field2.GetHashCode() protected abstract int GetClassHashCode(); public bool IsInterimDirty { get { return _isInterimDirty; } } public void SetIterimDirtyState() { _isInterimDirty = this.IsDirty; } public void MakeCleanIfInterimClean() { if (!IsInterimDirty) MakeClean(); } ///  /// Must be overridden with whatever valid tests are needed to make sure required field values are present. ///  public abstract bool IsValid { get; } } 

}

以及一个界面

 public interface ISmartWrap { bool IsDirty { get; set; } void MakeClean(); bool IsInterimDirty { get; } void SetIterimDirtyState(); void MakeCleanIfInterimClean(); } 

这允许我进行部分保存,如果还有其他要保存的细节,则保留IsDirty状态。 不完美,但涵盖了很多方面。

临时IsDirty State的使用示例(为清楚起见,错误包装和validation已删除):

  area.SetIterimDirtyState(); if (!UpdateClaimAndStatus(area)) return false; area.MakeCleanIfInterimClean(); return true; 

这对于大多数情况都是好的,但是对于某些类,我想要使用原始数据的支持字段测试每个字段,并返回更改列表或至少更改字段的枚举。 随着字段的更改,我可以将其推送到消息链中,以便有选择地更新远程缓存中的字段。

另一种方法是将GetHashCode()方法重写为如下所示:

 public override int GetHashCode() // or call it GetChangeHash or somthing if you dont want to override the GetHashCode function... { var sb = new System.Text.StringBuilder(); sb.Append(_dateOfBirth); sb.Append(_marital); sb.Append(_gender); sb.Append(_notes); sb.Append(_firstName); sb.Append(_lastName); return sb.ToString.GetHashCode(); } 

从数据库加载后,获取对象的哈希码。 然后在您保存之前检查当前哈希码是否等于先前的哈希码。 如果它们是相同的,请不要保存。

编辑:

正如人们已经指出这导致哈希码改变 – 因为我使用Guids来识别我的对象,我不介意哈希码是否改变。

EDIT2:

由于人们不喜欢更改哈希代码,而不是覆盖GetHashCode方法,只需调用方法即可。 重点是检测变化,而不是我是否使用guids或hashcodes进行对象识别。