如何处理相关班级中的属性变化事件?
我将旧版应用程序从C迁移到C#(.NET Core 5),并实施更现代的软件开发实践。 C应用实际上在RAM中具有所有数据,所有这些数据均在全球范围内可用。这对于简单的应用程序非常有用,但违反了封装原则的挑剔。以面向对象的方式执行此操作的最接近的是使数据存储statation
(由于明显的原因,我不想这样做)。
手头的应用技术高度技术,涉及大量数学。先前的应用程序根据需要计算了许多派生值(有时会反复!),但是这并不能利用多线程或缓存。
为了展示一个更简单但类似的例子,假设我们有一个计划来管理与棒球团队相关的成本。一个团队拥有自己的固定运营成本,以及一名经理(薪水),对许多球员(每个人都有薪水)为零。
这是我重新设计的数据模型:
public class Team {
public int Costs { get; set; }
public Manager Manager { get; set; }
public HashSet<Player> Players { get; set; }
private int _operatingCosts;
public int OperatingCosts {
get {
return this._operatingCosts;
}
private set {
// Note the private setter -- this should only be recalculated within this
// method from its own values.
this._operatingCosts = value;
}
}
}
public class Manager {
public string Name { get; set; }
public int Salary { get; set; }
}
public class Player {
public string Name { get; set; }
public int Salary { get; set; }
}
C代码将具有运行并将其手指放在所有数组中的函数,找到适当的元素并将其添加。在重写的C#代码中,这些计算中的大多数都相当简单,因此最好将它们实现为仅读取的属性属性。
public class Manager {
public int MonthlySalary {
get {
return this.Salary / 12;
}
}
}
当试图计算运行团队的总成本时,挑战就会出现。这可能是一个复杂的操作,我不想每次都必须运行,因此我想缓存一个值,并且只有在事件发射时重新计算它。我们知道哪些变量会导致更改,因此我们可以从因属性的设置器中启动事件。
public class Team : INotifyPropertyChanged {
private int _costs;
public int Costs {
get {
return this._costs;
}
set {
this._costs = value;
NotifyPropertyChanged(); // Fire recalculation on cost change.
}
}
public int Name {
get; set; // No INotifyPropertyChanged needed; cached values aren't
// dependent on this.
}
private int _totalOperatingCost;
public int TotalOperatingCost {
get {
return this._totalOperatingCost;
}
private set {
this._totalOperatingCost = value;
}
}
protected void RecalculateCosts() {
this._totalOperatingCost = this.Team.Cost + this.Manager.Salary + this.Players.Sum(p => p.Salary);
}
}
这与InotifyPropertychanged
- 我在字段上触发重新计算,并且重新计算逻辑在一个地方和所有位置,所有的地方都在inotifypropertychanged
- 我发射property>很好。
现在是问题:如果经理的薪水改变会发生什么?
public class Manager : INotifyPropertyChanged {
private int _salary;
public int Salary {
get {
return this._salary;
}
set {
this._salary = value;
NotifyParentPropertyChanged();
}
}
}
Manager
类不包含对他所属的Team
的引用。当父母知道自己的孩子时,将有很大的潜力保留从子豆到父母的反向。因此,如果我在此处调用notifyPropertyChanged()
在这里,Manager
将更新他自己的计算,但不知道它需要在父级上更新。这也大多是使用事件解决的,但是有一个很大的障碍:
public class Team {
public string Name { ... }
private Manager _manager;
public Manager Manager {
get {
return this._manager;
}
set {
if (this._manager is not null) {
this._manager.PropertyChanged -= ProcessParentPropertyChangedEvent;
}
this._manager = value;
NotifyPropertyChanged();
if (this._manager is not null) {
this._manager.PropertyChanged += ProcessParentPropertyChangedEvent;
}
}
}
public Team() {
if (this.Manager is not null) {
this.Manager.PropertyChanged += ProcessParentPropertyChangedEvent;
}
}
public static void ProcessParentPropertyChangedEvent(object sender, EventArgs e) {
// ^^^^^^ THIS IS A STATIC METHOD, so "this" is unavailable.
//
// When called from Manager.Salary, sender.GetType().Name = "Manager" and
// e can ONLY carry information from the Manager and knows nothing about
// the Team.
//
// Literally nothing in this method knows about the current team, or I believe
// can know about the team to which the manager belongs.
}
}
public class Manager {
private int _salary;
public int Salary {
get {
return this._salary;
}
set {
this._salary = value;
NotifyParentPropertyChanged();
}
}
}
事件正确触发,但是由于回调方法是static
,因此没有链接到team
谁的>经理
的薪水刚刚更新。在静态方法。
同样,我们也可能需要从团队中增加或减去球员,这也应迫使我们的团队成本重新计算。我沿着这条路走了一些inotifyCollectionChanged
,但是没有运气 - 我们仍然以静态方法最终没有有关更改需要应用于何处的静态方法。
我相信我的选择如下:
(1)包括对父元素的回报。这似乎有些简单,但是这里可能会出错很多,垃圾收集可能是其中之一。内存中可能会有大约一百万个元素,而且我不确定基本上在双连接图中的所有事物都会表现如何。
(2)我研究了调解人模式,但是我不相信这仍然可以解决我的问题,而没有大量的会计会计。它还需要将Mediator
类注入我的所有数据元素,并要求我对基本的.NET .NET通用集合类型使用扩展名 - 我不想被迫始终使用自定义myPlayerSlist
或类似类型的类型,而不是更常见的list&lt; player&gt;
- 尤其是当不使用自定义列表时,当重新计算时可能会以微妙而无声的错误结束只是没有完全提出正确的值。
我有什么选择?我是否被迫在整个模型中携带大量的反应?
我不知道的事件处理模型有不同的模型吗?我一直在使用inotifyPropertychanged
,因为我已经习惯了WPF上下文,而且非常快 - 在我的计算机上60毫秒内,有1,000,000个呼叫。也就是说,我不知道是否还有其他事件处理范例可以处理此类案例。
这似乎太简单了一个问题,以至于以前没有出现和解决其他人。非常感谢!
I'm migrating a legacy application from C to C# (.NET Core 5), and implementing more modern software development practices. The C application had literally all of its data in structures in RAM, all of which were globally available. This is great for simple applications but violates the heck out of the principle of encapsulation. The closest equivalent of doing this in an object-oriented fashion is to make the datastore static
(which I don't want to do, for obvious reasons).
The application at hand is highly technical and involves a great deal of math. The previous application calculated a lot of derived values (sometimes repeatedly!) on demand, however this doesn't take advantage of multi-threading or caching.
To demonstrate a simpler yet analogous example, let's say we have a program to manage the costs associated with a baseball team. A team has its own fixed operating costs, as well as one manager (who has a salary), and zero to many players (who also each have salaries).
This is my redesigned data model:
public class Team {
public int Costs { get; set; }
public Manager Manager { get; set; }
public HashSet<Player> Players { get; set; }
private int _operatingCosts;
public int OperatingCosts {
get {
return this._operatingCosts;
}
private set {
// Note the private setter -- this should only be recalculated within this
// method from its own values.
this._operatingCosts = value;
}
}
}
public class Manager {
public string Name { get; set; }
public int Salary { get; set; }
}
public class Player {
public string Name { get; set; }
public int Salary { get; set; }
}
The C code would have a function that runs and puts its fingers in all of the arrays, finds the appropriate elements, and adds them up. In the rewritten C# code, MOST of these calculations are reasonably simple, so they probably are best implemented as read-only calculated properties.
public class Manager {
public int MonthlySalary {
get {
return this.Salary / 12;
}
}
}
The challenge comes when trying to calculate the total cost of running the team. This might be a complex operation that I don't want to have to run each time, so I'll want to cache a value and only recalculate it when an event fires. We know which variables cause the change, so we can fire the event from within the dependent properties' setters.
public class Team : INotifyPropertyChanged {
private int _costs;
public int Costs {
get {
return this._costs;
}
set {
this._costs = value;
NotifyPropertyChanged(); // Fire recalculation on cost change.
}
}
public int Name {
get; set; // No INotifyPropertyChanged needed; cached values aren't
// dependent on this.
}
private int _totalOperatingCost;
public int TotalOperatingCost {
get {
return this._totalOperatingCost;
}
private set {
this._totalOperatingCost = value;
}
}
protected void RecalculateCosts() {
this._totalOperatingCost = this.Team.Cost + this.Manager.Salary + this.Players.Sum(p => p.Salary);
}
}
This has been working really well with INotifyPropertyChanged
-- I fire a PropertyChanged
event on fields that should trigger a recalculation, and the recalculation logic is there and in one place, and all is well.
Now here's the problem: what happens if the manager's salary changes?
public class Manager : INotifyPropertyChanged {
private int _salary;
public int Salary {
get {
return this._salary;
}
set {
this._salary = value;
NotifyParentPropertyChanged();
}
}
}
The Manager
class does not contain a reference to the Team
to which he belongs. There's a lot of potential for error retaining a backreference from the child bean to the parent when the parent knows about its own children. So if I call NotifyPropertyChanged()
here, the Manager
will update his own calculations but it has no idea it needs to update on the parent. This is also mostly resolvable using events, but there's a big hitch:
public class Team {
public string Name { ... }
private Manager _manager;
public Manager Manager {
get {
return this._manager;
}
set {
if (this._manager is not null) {
this._manager.PropertyChanged -= ProcessParentPropertyChangedEvent;
}
this._manager = value;
NotifyPropertyChanged();
if (this._manager is not null) {
this._manager.PropertyChanged += ProcessParentPropertyChangedEvent;
}
}
}
public Team() {
if (this.Manager is not null) {
this.Manager.PropertyChanged += ProcessParentPropertyChangedEvent;
}
}
public static void ProcessParentPropertyChangedEvent(object sender, EventArgs e) {
// ^^^^^^ THIS IS A STATIC METHOD, so "this" is unavailable.
//
// When called from Manager.Salary, sender.GetType().Name = "Manager" and
// e can ONLY carry information from the Manager and knows nothing about
// the Team.
//
// Literally nothing in this method knows about the current team, or I believe
// can know about the team to which the manager belongs.
}
}
public class Manager {
private int _salary;
public int Salary {
get {
return this._salary;
}
set {
this._salary = value;
NotifyParentPropertyChanged();
}
}
}
The event fires properly but because the callback method is static
, there's no link to the Team
whose Manager
's salary just updated. Player
would have a similar problem, there's no way to inform the Team
class that a Player
's salary updated since the recalculation would take place within a static method.
Similarly, we could need to add or subtract players from the team as well, which should also force a recalculation of our team costs. I've gone down this road some with INotifyCollectionChanged
, but no luck -- we still end up in a static method with no information about where the changes need to be applied to.
I believe my options are as follows:
(1) Include backreferences to parent elements. This seems somewhat straightforward, but there is a lot that can go wrong here, and garbage collection may be one of them. There are probably going to be about a million elements in memory, and I'm not sure how well this would perform by everything basically being in a doubly-connected graph.
(2) I've looked into the Mediator pattern, but I'm not convinced this still solves my problems without a great deal of error-prone accounting. It also requires the Mediator
class to be injected into all of my data elements, and requires me to use extensions for base .NET generic collections types -- I don't want to be forced to always use custom types like MyPlayersList
or similar, instead of the more common List<Player>
-- especially when NOT using the custom list would likely end in a subtle and silent bug when a recalculation just didn't quite pull the right values.
What are my options? Am I forced to carry tons of backreferences throughout the model?
Is there a different model for event handling that I'm not aware of? I've been working with INotifyPropertyChanged
because I'm used to it from a WPF context and it's really fast -- 1,000,000 calls in 60 ms on my machine. That said I don't know if there are any other event handling paradigms that exist to handle this type of case.
This seems like way too straightforward an issue to not have appeared and been solved by someone else before. Thanks so much!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论