c#将类属性标记为脏

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/805505/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 01:55:42  来源:igfitidea点击:

c# marking class property as dirty

c#classproperties

提问by

The following is a simple example of an enum which defines the state of an object and a class which shows the implementation of this enum.

下面是一个简单的枚举示例,它定义了一个对象的状态和一个显示该枚举实现的类。

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

when populating the class object with data from the database, we set the enum value to "clean". with the goal of keeping most of the logic out of the presentation layer, how can we set the enum value to "dirty" when a property is changed.

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

i was thinking something along the lines of;

我在想一些事情;

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

in the setter of each property of the class.

在类的每个属性的 setter 中。

does this sound like a good idea, does anyone have any better ideas on how the dirty flag can be assigned without doing so in the presentation layer.

这听起来是个好主意吗,有没有人对如何分配脏标志有更好的想法,而无需在表示层中这样做。

回答by Jon Skeet

One option is to change it on write; another is to keep a copy of all the original values and compute the dirtiness when anyone asks for it. That has the added benefit that you can tell exactly whichfields have changed (and in what way) which means you can issue minimal update statements and make merge conflict resolution slightly easier.

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

You also get to put all the dirtiness-checking in one place, so it doesn't pollute the rest of your code.

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

I'm not saying it's perfect, but it's an option worth considering.

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

回答by Treb

Your approach is basically how I would do it. I would just remove the setter for the Status property:

你的方法基本上就是我会怎么做。我只想删除 Status 属性的设置器:

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

and instead add a function

而是添加一个函数

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

As well as SetStatusDeleted()and SetStatusPurged(), because I find it better indicates the intention.

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

Edit

编辑

Having read the answer by Jon Skeet, I need to reconsider my approach ;-) For simple objects I would stick with my way, but if it gets more complex, his proposal would lead to much better organised code.

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

回答by CSharper

If your Example_Class is lightweight, consider storing the original state and then comparing the current state to the original in order to determine the changes. If not your approach is the best because stroing the original state consumes a lot of system resources in this case.

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

回答by Marc Gravell

When you really do want a dirty flag at the class level (or, for that matter, notifications) - you can use tricks like below to minimise the clutter in your properties (here showing both IsDirtyand PropertyChanged, just for fun).

当您确实想要在类级别(或就此而言,通知)的脏标志时 - 您可以使用如下技巧来最大程度地减少属性中的混乱(此处同时显示IsDirtyPropertyChanged,只是为了好玩)。

Obviously it is a trivial matter to use the enum approach (the only reason I didn't was to keep the example simple):

显然,使用 enum 方法是一件小事(我没有这样做的唯一原因是保持示例简单):

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<T>(ref T field, T value, string propertyName) {
        if (!EqualityComparer<T>.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));
        }
    }
}

You might also choose to push some of that into an abstract base class, but that is a separate discussion

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

回答by Pondidum

Another method is to override the GetHashCode() method to somthing like this:

另一种方法是将 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();
}

Once loaded from the database, get the hash code of the object. Then just before you save check if the current hash code is equal to the previous hash code. if they are the same, don't save.

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

Edit:

编辑:

As people have pointed out this causes the hash code to change - as i use Guids to identify my objects, i don't mind if the hashcode changes.

正如人们所指出的那样,这会导致哈希码发生变化——当我使用 Guids 来识别我的对象时,我不介意哈希码是否发生变化。

Edit2:

编辑2:

Since people are adverse to changing the hash code, instead of overriding the GetHashCode method, just call the method something else. The point is detecting a change not whether i use guids or hashcodes for object identification.

由于人们反对更改哈希码,因此不要覆盖 GetHashCode 方法,只需调用其他方法即可。关键是检测变化而不是我是否使用 guid 或哈希码进行对象识别。

回答by Frederik Gheysels

If you want to implement it in this way, and you want to reduce the amount of code, you might consider applying Aspect Oriented Programming.

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

You can for instance use a compile-time weaver like PostSharp, and create an 'aspect' that can be applied to properties. This aspect then makes sure that your dirty flag is set when appropriate.

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

The aspect can look like this:

该方面可能如下所示:

[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, this means that the classes for which you want to implement ChangeTracking, should implement the IChangeTrackableinterface (custom interface), which has at least the 'Status' property.

当然,这意味着您要为其实现 ChangeTracking 的类应该实现IChangeTrackable接口(自定义接口),该接口至少具有“状态”属性。

You can also create a custom attribute ChangeTrackingProperty, and make sure that the aspect that has been created above, is only applied to properties that are decorated with this ChangeTrackingPropertyattribute.

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

For instance:

例如:

public class Customer : IChangeTrackable
{
    public DirtyState Status
    {
        get; set;
    }

    [ChangeTrackingProperty]
    public string Name
    { get; set; }
}

This is a little bit how I see it. You can even make sure that PostSharp checks at compile-time whether classes that have properties that are decorated with the ChangeTrackingProperty attribute, implement the IChangeTrackable interface.

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

回答by Mark Broadhurst

Take a look at PostSharp (http://www.postsharp.org/). You can easily create a Attribute which marks it as dirty you can add the attrubute to each property that needs it and it keeps all your code in one place.

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

Roughly speaking Create an interface which has your status in make the class implement it. Create an attribute which can be applied on properties and cast to your interface in order to set the value when something changes one of the marked properties.

粗略地说,创建一个接口,该接口在使类实现它时具有您的地位。创建一个可以应用于属性并转换到您的界面的属性,以便在某个标记的属性发生变化时设置该值。

回答by Gishu

Apart from the advice of 'consider making your type immutable', here's something I wrote up (and got Jon and Marc to teach me something along the way)

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

public class Example_Class
{    // snip
     // all properties are public get and private set

     private Dictionary<string, Delegate> m_PropertySetterMap;

     public Example_Class()
     {
        m_PropertySetterMap = new Dictionary<string, Delegate>();
        InitializeSettableProperties();
     }
     public Example_Class(long id, string name):this()
     {   this.ID = id;    this.Name = name;   }

     private void InitializeSettableProperties()
     {
        AddToPropertyMap<long>("ID",  value => { this.ID = value; });
        AddToPropertyMap<string>("Name", value => { this.Name = value; }); 
     }
     // jump thru a hoop because it won't let me cast an anonymous method to an Action<T>/Delegate
     private void AddToPropertyMap<T>(string sPropertyName, Action<T> setterAction)
     {   m_PropertySetterMap.Add(sPropertyName, setterAction);            }

     public void SetProperty<T>(string propertyName, T value)
     {
        (m_PropertySetterMap[propertyName] as Action<T>).Invoke(value);
        this.Status = StatusEnum.Dirty;
     }
  }

You get the idea.. possible improvements: Use constants for PropertyNames & check if property has really changed. One drawback here is that

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

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

回答by MaxOvrdrv

This method is based on a set of different concepts provided in this thread. I thought i'd put it out there for anyone that is looking for a way to do this cleanly and efficiently, as i was myself.

此方法基于此线程中提供的一组不同概念。我想我会把它发布给任何正在寻找一种干净有效的方法的人,就像我自己一样。

The key of this hybrid concept is that:

这种混合概念的关键在于:

  1. You don't want to duplicate the data to avoid bloating and resource hogging;
  2. You want to know when the object's properties have changed from a given original/clean state;
  3. You want to have the IsDirty flag be both accurate, and require little processing time/power to return the value; and
  4. You want to be able to tell the object when to consider itself clean again. This is especially useful when building/working within the UI.
  1. 您不想复制数据以避免膨胀和资源占用;
  2. 您想知道对象的属性何时从给定的原始/干净状态发生变化;
  3. 您希望 IsDirty 标志既准确又需要很少的处理时间/功率来返回值;和
  4. 您希望能够告诉对象何时再次认为自己是干净的。这在 UI 中构建/工作时特别有用。

Given those requirements, this is what i came up with, and it seems to be working perfectly for me, and has become very useful when working against UIs and capturing user changes accurately. I have also posted an "How to use" below to show you how I use this in the UI.

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

The Object

物体

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

It's simple enough and addresses all of our requirements:

它足够简单,可以满足我们的所有要求:

  1. The data is NOT duplicated for the dirty check...
  2. This takes into account all property changes scenarios (see scenarios below)...
  3. When you call the IsDirty property, a very simple and small Equals operation is performed and it is fully customizable via the GetHashCode override...
  4. By calling the MakeMeClean method, you now have a clean object again!
  1. 脏检查的数据不会重复......
  2. 这考虑了所有属性更改方案(请参阅下面的方案)...
  3. 当您调用 IsDirty 属性时,将执行一个非常简单和小的 Equals 操作,并且它可以通过 GetHashCode 覆盖完全自定义...
  4. 通过调用 MakeMeClean 方法,您现在又拥有了一个干净的对象!

Of course you can adapt this to encompass a bunch of different states... it's really up to you. This example only shows how to have a proper IsDirty flag operation.

当然,您可以将其调整为包含一系列不同的状态……这完全取决于您。此示例仅显示如何进行正确的 IsDirty 标志操作。

Scenarios
Let's go over some scenarios for this and see what comes back:

场景
让我们回顾一些场景,看看会返回什么:

  • Scenario 1
    New object is created using empty constructor,
    Property Name changes from "" to "James",
    call to IsDirty returns True! Accurate.

  • Scenario 2
    New object is created using paramters of "John" and 12345,
    Property Name changes from "John" to "James",
    Property Name changes back from "James" to "John",
    Call to IsDirty returns False. Accurate, and we didn't have to duplicate the data to do it either!

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

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

How to use, a WinForms UI example
This is only an example, you can use this in many different ways from a UI.

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

Let's say you have a two forms ([A] and [B]).

The first([A]) is your main form, and the second([B]) is a form that allows the user to change the values within the MySmartObject.

Both the [A] and the [B] form have the following property declared:

假设您有两种形式([A] 和 [B])。

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

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

public MySmartObject UserKey { get; set; }

When the user clicks a button on the [A] form, an instance of the [B] form is created, its property is set and it is displayed as a dialog.

After form [B] returns, the [A] form updates its property based on the [B] form's IsDirty check. Like this:

当用户单击 [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();
    }
}

Also, in [B], when it is closing, you can check and prompt the user if they are closing the form with unsaved changes in it, like so:

此外,在 [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 (e.g.: 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;
            }

        }

    }

As you can see from the examples above, this can be a very useful thing to have since it really streamlines the UI.

正如您从上面的示例中看到的那样,这可能是一件非常有用的事情,因为它确实简化了 UI。

Caveats

注意事项

  • Every time you implement this, you have to customize it to the object you're using. E.g.: there's no "easy" generic way of doing this without using reflection... and if you use reflection, you lose efficiency, especially in large and complex objects.
  • 每次实现这一点时,您都​​必须根据您正在使用的对象对其进行自定义。例如:在不使用反射的情况下,没有“简单”的通用方法可以做到这一点……如果使用反射,则会降低效率,尤其是在大型复杂对象中。

Hopefully this helps someone.

希望这有助于某人。

回答by Michael Khalsa

Here is how i do it.

这是我如何做到的。

In cases where i do not need to test for specific fields being dirty, I have an abstract class:

在我不需要测试特定字段是否脏的情况下,我有一个抽象类:

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

    /// <summary>
    /// Must be overridden with whatever valid tests are needed to make sure required field values are present.
    /// </summary>
    public abstract bool IsValid { get; }
}

}

}

As well as an interface

以及一个界面

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

This allows me to do partial saves, and preserve the IsDirty state if there is other details to save. Not perfect, but covers a lot of ground.

这允许我进行部分保存,并在有其他细节要保存时保留 IsDirty 状态。不完美,但涵盖了很多领域。

Example of usage with interim IsDirty State (Error wrapping and validation removed for clarity):

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

            area.SetIterimDirtyState();

            if (!UpdateClaimAndStatus(area))
                return false;

            area.MakeCleanIfInterimClean();

            return true;

This is good for most scenarios, however for some classes i want to test for each field with a backing field of original data, and either return a list of changes or at least an enum of fields changed. With an enum of fields changed i can then push that up through a message chain for selective update of fields in remote caches.

这适用于大多数场景,但是对于某些类,我想用原始数据的支持字段测试每个字段,并返回更改列表或至少更改的字段枚举。更改字段枚举后,我可以通过消息链将其向上推送,以选择性更新远程缓存中的字段。