C# 在对象上实现更改跟踪的最佳方法是什么
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2363801/
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
What would be the best way to implement change tracking on an object
提问by user137348
I have a class which contains 5 properties.
我有一个包含 5 个属性的类。
If any value is assingned to any of these fields, an another value (for example IsDIrty) would change to true.
如果将任何值分配给这些字段中的任何一个,则另一个值(例如 IsDIrty)将更改为 true。
public class Class1
{
bool IsDIrty {get;set;}
string Prop1 {get;set;}
string Prop2 {get;set;}
string Prop3 {get;set;}
string Prop4 {get;set;}
string Prop5 {get;set;}
}
采纳答案by Binary Worrier
To do this you can't really use automatic getter & setters, and you need to set IsDirty in each setter.
为此,您不能真正使用自动 getter 和 setter,您需要在每个 setter 中设置 IsDirty。
I generally have a "setProperty" generic method that takes a ref parameter, the property name and the new value. I call this in the setter, allows a single point where I can set isDirty and raise Change notification events e.g.
我通常有一个“setProperty”泛型方法,它接受一个 ref 参数、属性名称和新值。我在 setter 中调用它,允许我可以设置 isDirty 并引发更改通知事件的单点,例如
protected bool SetProperty<T>(string name, ref T oldValue, T newValue) where T : System.IComparable<T>
{
if (oldValue == null || oldValue.CompareTo(newValue) != 0)
{
oldValue = newValue;
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
isDirty = true;
return true;
}
return false;
}
// For nullable types
protected void SetProperty<T>(string name, ref Nullable<T> oldValue, Nullable<T> newValue) where T : struct, System.IComparable<T>
{
if (oldValue.HasValue != newValue.HasValue || (newValue.HasValue && oldValue.Value.CompareTo(newValue.Value) != 0))
{
oldValue = newValue;
PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(name));
}
}
回答by Dan Tao
Set IsDirty
to true in all of your setters.
IsDirty
在所有的 setter 中设置为 true。
You might also consider making the setter for IsDirty
private (or protected, if you may have child classes with additional properties). Otherwise you could have code outside of the class negating its internal mechanism for determining dirtiness.
您还可以考虑将 setter 设置为IsDirty
私有(或受保护,如果您可能有具有附加属性的子类)。否则,您可能会在类之外使用代码否定其确定脏的内部机制。
回答by deostroll
Carefully consider the underlying purpose the object tracking is required? Suppose if it is something like other objects have to do something based on another object's state, then consider implementing the observer design pattern.
仔细考虑需要对象跟踪的潜在目的?假设如果其他对象必须根据另一个对象的状态做某事,那么请考虑实现观察者设计模式。
If its something small consider implementing the INotifyPropertyChangedinterface.
如果它很小,请考虑实现INotifyPropertyChanged接口。
回答by Andy Shellam
Dan's solution is perfect.
丹的解决方案是完美的。
Another option to consider if you're going to have to do this on multiple classes (or maybe you want an external class to "listen" for changes to the properties):
如果您必须在多个类上执行此操作(或者您可能希望外部类“侦听”属性更改),请考虑另一个选择:
- Implement the
INotifyPropertyChanged
interface in an abstract class - Move the
IsDirty
property to your abstract class - Have
Class1
and all other classes that require this functionality to extend your abstract class - Have all your setters fire the
PropertyChanged
event implemented by your abstract class, passing in their name to the event - In your base class, listen for the
PropertyChanged
event and setIsDirty
to true when it fires
INotifyPropertyChanged
在抽象类中实现接口- 将
IsDirty
属性移动到您的抽象类 - 拥有
Class1
和所有其他需要此功能来扩展抽象类的类 - 让所有的 setter 触发
PropertyChanged
抽象类实现的事件,将它们的名称传递给事件 - 在您的基类中,侦听
PropertyChanged
事件并IsDirty
在触发时设置为 true
It's a bit of work initially to create the abstract class, but it's a better model for watching for data changes as any other class can see when IsDirty
(or any other property) changes.
最初创建抽象类需要一些工作,但它是一个更好的模型来观察数据变化,因为任何其他类都可以看到何时IsDirty
(或任何其他属性)发生变化。
My base class for this looks like the following:
我的基类如下所示:
public abstract class BaseModel : INotifyPropertyChanged
{
/// <summary>
/// Initializes a new instance of the BaseModel class.
/// </summary>
protected BaseModel()
{
}
/// <summary>
/// Fired when a property in this class changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Triggers the property changed event for a specific property.
/// </summary>
/// <param name="propertyName">The name of the property that has changed.</param>
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Any other model then just extends BaseModel
, and calls NotifyPropertyChanged
in each setter.
任何其他模型都只是 extends BaseModel
,并调用NotifyPropertyChanged
每个 setter 。
回答by Daniel Earwicker
If there are a very large number of such classes, all having that same pattern, and you frequently have to update their definitions, consider using code generation to automatically spit out the C# source files for all the classes, so that you don't have to manually maintain them. The input to the code generator would just be a simple text file format that you can easily parse, stating the names and types of the properties needed in each class.
如果有大量这样的类,所有类都具有相同的模式,并且您经常需要更新它们的定义,请考虑使用代码生成来自动吐出所有类的 C# 源文件,这样您就没有手动维护它们。代码生成器的输入只是一个简单的文本文件格式,您可以轻松解析它,说明每个类中所需的属性的名称和类型。
If there are just a small number of them, or the definitions change very infrequently during your development process, then it's unlikely to be worth the effort, in which case you may as well maintain them by hand.
如果它们的数量很少,或者在您的开发过程中定义很少更改,那么不太可能值得付出努力,在这种情况下,您不妨手动维护它们。
Update:
更新:
This is probably way over the top for a simple example, but it was fun to figure out!
对于一个简单的例子来说,这可能是最重要的,但弄清楚这很有趣!
In Visual Studio 2008, if you add a file called CodeGen.tt
to your project and then paste this stuff into it, you'll have the makings of a code generation system:
在 Visual Studio 2008 中,如果您添加一个调用CodeGen.tt
到您的项目的文件,然后将这些内容粘贴到其中,您将拥有一个代码生成系统:
<#@ template debug="false" hostspecific="true" language="C#v3.5" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#
// You "declare" your classes here, as in these examples:
var src = @"
Foo: string Prop1,
int Prop2;
Bar: string FirstName,
string LastName,
int Age;
";
// Parse the source text into a model of anonymous types
Func<string, bool> notBlank = str => str.Trim() != string.Empty;
var classes = src.Split(';').Where(notBlank).Select(c => c.Split(':'))
.Select(c => new
{
Name = c.First().Trim(),
Properties = c.Skip(1).First().Split(',').Select(p => p.Split(' ').Where(notBlank))
.Select(p => new { Type = p.First(), Name = p.Skip(1).First() })
});
#>
// Do not edit this file by hand! It is auto-generated.
namespace Generated
{
<# foreach (var cls in classes) {#> class <#= cls.Name #>
{
public bool IsDirty { get; private set; }
<# foreach (var prop in cls.Properties) { #>
private <#= prop.Type #> _storage<#= prop.Name #>;
public <#= prop.Type #> <#= prop.Name #>
{
get { return _storage<#= prop.Name #>; }
set
{
IsDirty = true;
_storage<#= prop.Name #> = value;
}
} <# } #>
}
<# } #>
}
There's a simple string literal called src
in which you declare the classes you need, in a simple format:
有一个简单的字符串文字src
,您可以在其中以简单的格式声明所需的类:
Foo: string Prop1,
int Prop2;
Bar: string FirstName,
string LastName,
int Age;
So you can easily add hundreds of similar declarations. Whenever you save your changes, Visual Studio will execute the template and produce CodeGen.cs
as output, which contains the C# source for the classes, complete with the IsDirty
logic.
因此,您可以轻松添加数百个类似的声明。每当您保存更改时,Visual Studio 都会执行模板并生成CodeGen.cs
输出,其中包含类的 C# 源代码以及完整的IsDirty
逻辑。
You can change the template of what is produced by altering the last section, where it loops through the model and produces the code. If you've used ASP.NET, it's similar to that, except generating C# source instead of HTML.
您可以通过更改最后一部分来更改生成的模板,它会在其中循环遍历模型并生成代码。如果您使用过 ASP.NET,则它与此类似,不同之处在于生成 C# 源代码而不是 HTML。
回答by Will Marcouiller
Both Dan's and Andy Shellam's answers are my favorites.
Dan 和 Andy Shellam 的回答都是我的最爱。
In anyway, if you wanted to keep TRACK of you changes, like in a log or so, you might consider the use of a Dictionary that would add all of your property changes when they are notified to have changed. So, you could add the change into your Dictionary with a unique key, and keep track of your changes. Then, if you wish to Roolback in-memory the state of your object, you could this way.
无论如何,如果您想保留对您的更改的跟踪,例如在日志中,您可以考虑使用字典,在通知更改时添加所有属性更改。因此,您可以使用唯一键将更改添加到您的字典中,并跟踪您的更改。然后,如果您希望在内存中回滚对象的状态,您可以通过这种方式。
EDITHere's what Bart de Smet uses to keep track on property changes throughout LINQ to AD. Once the changes have been committed to AD, he clears the Dictionary. So, when a property changes, because he implemented the INotifyPropertyChanged interface, when a property actually changed, he uses a Dictionary> as follows:
编辑这是 Bart de Smet 用来跟踪整个 LINQ to AD 中的属性更改的内容。将更改提交给 AD 后,他会清除字典。所以,当一个属性改变时,因为他实现了INotifyPropertyChanged接口,当一个属性真正改变时,他使用Dictionary>如下:
/// <summary>
/// Update catalog; keeps track of update entity instances.
/// </summary>
private Dictionary<object, HashSet<string>> updates
= new Dictionary<object, HashSet<string>>();
public void UpdateNotification(object sender, PropertyChangedEventArgs e)
{
T source = (T)sender;
if (!updates.ContainsKey(source))
updates.Add(source, new HashSet<string>());
updates[source].Add(e.PropertyName);
}
So, I guess that if Bart de Smet did that, this is somehow a practice to consider.
所以,我想如果 Bart de Smet 这样做了,这在某种程度上是一种值得考虑的做法。
回答by rohancragg
This is something that is built into the BusinessBaseclass in Rocky Lhokta's CLSAframework, so you could always go and look at how it's done...
这是Rocky Lhokta 的CLSA框架中的BusinessBase类中内置的东西,因此您可以随时查看它是如何完成的...
回答by iCode
I know this is an old thread, but I think Enumerations will not work with Binary Worrier's solution. You will get a design-time error message that the enum property Type "cannot be used as type parameter 'T' in the generic type or method"..."SetProperty(string, ref T, T)'. There is no boxing conversion...".
我知道这是一个旧线程,但我认为枚举不适用于 Binary Worrier 的解决方案。您将收到一条设计时错误消息,指出枚举属性类型“不能用作泛型类型或方法中的类型参数 'T'”...“SetProperty(string, ref T, T)'。没有装箱转换...”。
I referenced this stackoverflow post to solve the issue with enumerations: C# boxing enum error with generics
我引用了这个stackoverflow帖子来解决枚举问题:C# boxing enum error with generics
回答by Nick N.
To support enums, please use the perfect solution of Binary Worrier and add the code below.
要支持枚举,请使用 Binary Worrier 的完美解决方案并添加下面的代码。
I have added Enum support for my own (and it was a pain), I guess this is nice to add as well.
我已经为我自己添加了 Enum 支持(这很痛苦),我想这也很好添加。
protected void SetEnumProperty<TEnum>(string name, ref TEnum oldEnumValue, TEnum newEnumValue) where TEnum : struct, IComparable, IFormattable, IConvertible
{
if (!(typeof(TEnum).IsEnum)) {
throw new ArgumentException("TEnum must be an enumerated type");
}
if (oldEnumValue.CompareTo(newEnumValue) != 0) {
oldEnumValue = newEnumValue;
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
_isChanged = true;
}
}
And implemented via:
并通过以下方式实施:
Public Property CustomerTyper As CustomerTypeEnum
Get
Return _customerType
End Get
Set(value As ActivityActionByEnum)
SetEnumProperty("CustomerType", _customerType, value)
End Set
End Property
回答by mamuesstack
I know that it's been a while since you asked this. If you're still interested to keep your classes clean and simple without the need of deriving from base classes, I would suggest to use PropertyChanged.Fodythat has an IsChanged Flagimplemented
我知道你问这个已经有一段时间了。如果您仍然有兴趣在不需要从基类派生的情况下保持类的简洁和简单,我建议使用实现了IsChanged 标志的PropertyChanged.Fody