C# 禁用事件处理的设计模式
为了简单地说明我的困境,假设我有以下代码:C# 禁用事件处理的设计模式,c#,wpf,design-patterns,C#,Wpf,Design Patterns,为了简单地说明我的困境,假设我有以下代码: class A { // May be set by a code or by an user. public string Property { set { PropertyChanged(this, EventArgs.Empty); } } public EventHandler PropertyChanged; } class B { private A _a; pu
class A
{
// May be set by a code or by an user.
public string Property
{
set { PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
}
class B
{
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
_a.Property = "make a problem";
}
}
为了履行其职责,B类必须对A的PropertyChanged事件作出反应,但在某些情况下,B类也能够自行改变该属性。不幸的是,其他对象也可以与该属性交互
我需要一个顺序流的解决方案。也许我可以使用一个变量来禁用一个操作:
bool _dontDoThis;
void Handler(object s, EventArgs e)
{
if (_dontDoThis)
return;
// Do this!
}
public void MakeProblem()
{
_dontDoThis = true;
_a.Property = "make a problem";
_dontDoThis = false;
}
有更好的方法吗
其他考虑事项
- 我们无法更改A
- A是密封的
- 还有其他方与PropertyChanged事件有关,我不知道他们是谁。但是当我从B更新属性时,不应该也通知他们。但我无法将它们与活动断开,因为我不认识它们
- 如果同时有更多的线程可以与属性交互呢
我最初的问题是一个文本框(WPF),我想根据它的内容和焦点来补充它。所以我需要对TextChanged事件做出反应,如果它的来源是我的补语,我还需要省略该事件。在某些情况下,不应通知TextChanged事件的其他侦听器。某些处于特定状态和样式的字符串对其他字符串是不可见的。如果不处理您启动的事件非常重要,也许您应该更改设置属性的方式,以包括更改的发起人
public class MyEventArgs : EventArgs
{
public object Changer;
}
public void SetProperty(string p_newValue, object p_changer)
{
MyEventArgs eventArgs = new MyEventArgs { Changer = p_changer };
PropertyChanged(this, eventArgs);
}
然后在您的处理程序中,只需检查您是否不是发起人
我发现注册和成员的所有这些更改在多线程和可扩展性方面都存在很大问题。我认为您的解决方案是可能的,尽管我会在B中创建一个嵌套的IDisposable类,它对“using”做同样的事,或者在“finally”子句中添加“\u dontdotthis=false”
class A
{
// May be set by a code or by an user.
public string Property
{
set { if (!_dontDoThis) PropertyChanged(this, EventArgs.Empty); }
}
public EventHandler PropertyChanged;
bool _dontDoThis;
}
class B
{
private class ACallWrapper : IDisposable
{
private B _parent;
public ACallWrapper(B parent)
{
_parent = parent;
_parent._a._dontDoThis = true;
}
public void Dispose()
{
_parent._a._dontDoThis = false;
}
}
private A _a;
public B(A a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Who changed the Property?
}
public void MakeProblem()
{
using (new ACallWrapper(this))
_a.Property = "make a problem";
}
}
另一方面,如果这两个类在同一个程序集中,我会使用“internal”修饰符
internal bool _dontDoThis;
这样,您可以保持更好的OOP设计
此外,如果两个类都在同一个程序集上,我会在
// May be set by a code or by an user.
public string Property
{
set
{
internalSetProperty(value);
PropertyChanged(this, EventArgs.Empty);
}
}
internal internalSetProperty(string value)
{
// Code of set.
}
在这种情况下,B可以访问internalSetProperty而不触发PropertyChanged事件
线程同步:注意:下一节适用于WinForms-我不知道它是否也适用于WPF。
对于线程同步,因为我们指的是一个控件。您可以使用GUI线程机制进行同步:
class A : Control
{
public string Property
{
set
{
if (this.InvokeRequired)
{
this.Invoke((Action<string>)setProperty, value);
reutrn;
}
setProperty(value);
}
}
private void setProperty string()
{
PropertyChanged(this, EventArgs.Empty);
}
}
A类:控制
{
公共字符串属性
{
设置
{
if(this.invokererequired)
{
调用((操作)setProperty,value);
reutrn;
}
设置属性(值);
}
}
私有void setProperty字符串()
{
PropertyChanged(this,EventArgs.Empty);
}
}
我找到了一个关于第三方的解决方案,这些第三方连接到该属性,我们不想在该属性更改时取消它们
尽管有以下要求:
- 我们有能力超越A
- A有一个虚拟方法,该方法在属性更改时调用,并允许挂起要引发的事件
- 更改属性时立即引发该事件
class A
{
// May be set by a code or by an user.
public string Property
{
set { OnPropertyChanged(EventArgs.Empty); }
}
// This is required
protected virtual void OnPropertyChanged(EventArgs e)
{
PropertyChanged(this, e);
}
public EventHandler PropertyChanged;
}
// Inject MyA instead of A
class MyA : A
{
private bool _dontDoThis;
public string MyProperty
{
set
{
try
{
_dontDoThis = true;
Property = value;
}
finally
{
_dontDoThis = false;
}
}
}
protected override void OnPropertyChanged(EventArgs e)
{
// Also third parties will be not notified
if (_dontDoThis)
return;
base.OnPropertyChanged(e);
}
}
class B
{
private MyA _a;
public B(MyA a)
{
_a = a;
_a.PropertyChanged += Handler;
}
void Handler(object s, EventArgs e)
{
// Now we know, that the event is not raised by us.
}
public void MakeProblem()
{
_a.MyProperty = "no problem";
}
}
不幸的是,我们仍然使用backbool字段,并且假设只有一个线程。为了消除第一个问题,我们可以使用()建议的重构解决方案。首先,创建一次性包装:
class Scope
{
public bool IsLocked { get; set; }
public static implicit operator bool(Scope scope)
{
return scope.IsLocked;
}
}
class ScopeGuard : IDisposable
{
private Scope _scope;
public ScopeGuard(Scope scope)
{
_scope = scope;
_scope.IsLocked = true;
}
public void Dispose()
{
_scope.IsLocked = false;
}
}
然后MyProperty可能会被重构为:
private readonly Scope _dontDoThisScope = new Scope();
public string MyProperty
{
set
{
using (new ScopeGuard (_dontDoThisScope))
Property = value;
}
}
实际上,您正试图破坏事件委派机制,任何解决方案都将变得脆弱,因为对BCL的更新可能会破坏您的代码。可以使用反射设置背景字段。当然,这需要您拥有这样做的权限,并且查看问题的一般框架—您可能并不总是拥有所需的权限
public void MakeProblem()
{
if (_backingField == null) {
_backingField = = _a.GetType().GetField(fieldname)
}
_backingField.SetValue(_a,"make a problem");
}
但在我开始时,您正试图打破事件委派机制。其理念是,事件的接收者是独立的。禁用可能会导致很难找到bug,因为查看任何给定的代码都会发现它是正确的,但只有当您意识到某个狡猾的开发人员已经破解了委托机制时,您才会意识到为什么屏幕上显示的信息似乎是实际值的缓存版本。调试器显示属性的预期值,但由于事件已隐藏,因此从未触发负责更新显示的处理程序,因此显示旧版本(或者日志显示不正确的信息,因此,当您试图重新创建用户根据日志内容报告的问题时,您将无法重新创建,因为日志中的信息不正确,因为它是基于没有人入侵事件委派机制的原因。好问题
一般情况下,您不能随意处理密封类的事件处理程序。通常,您可以重写a
的假设OnPropertyChanged
,并基于某个标志是否引发事件。或者,您可以提供一个setter方法,该方法不引发事件,如@Vadim所建议。但是,如果a是sealed您最好的选择是向侦听器添加标志,就像您所做的那样。这将使您能够识别由B
引发的PropertyChanged
事件,但您将无法为其他侦听器抑制该事件
现在,由于您提供了上下文,在WPF中有一种方法可以做到这一点。需要做的就是B
的TextBox处理程序。TextChanged
需要设置e.Handled=\u dontdotthis
。如果添加了B
的一个,则会禁止所有其他侦听器的通知
// Create a new RoutedEventHandler
RoutedEventHandlerInfo routedEventHandlerInfo =
new RoutedEventHandlerInfo(handler, handledEventsToo);
// Get the entry corresponding to the given RoutedEvent
FrugalObjectList<RoutedEventHandlerInfo> handlers = (FrugalObjectList<RoutedEventHandlerInfo>)this[routedEvent];
if (handlers == null)
{
_entries[routedEvent.GlobalIndex] = handlers = new FrugalObjectList<RoutedEventHandlerInfo>(1);
}
// Add the RoutedEventHandlerInfo to the list
handlers.Add(routedEventHandlerInfo);
public static class UIElementExtensions
{
public static void InsertEventHandler(this UIElement element, int index, RoutedEvent routedEvent, Delegate handler)
{
// get EventHandlerStore
var prop = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.NonPublic | BindingFlags.Instance);
var eventHandlerStoreType = prop.PropertyType;
var eventHandlerStore = prop.GetValue(element, new object[0]);
// get indexing operator
PropertyInfo indexingProperty = eventHandlerStoreType.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(x => x.Name == "Item" && x.GetIndexParameters().Length == 1 && x.GetIndexParameters()[0].ParameterType == typeof(RoutedEvent));
object handlers = indexingProperty.GetValue(eventHandlerStore, new object[] { routedEvent });
if (handlers == null)
{
// just add the handler as there are none at the moment so it is going to be the first one
if (index != 0)
{
throw new ArgumentOutOfRangeException("index");
}
element.AddHandler(routedEvent, handler);
}
else
{
// create routed event handler info
var constructor = typeof(RoutedEventHandlerInfo).GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).Single();
var handlerInfo = constructor.Invoke(new object[] { handler, false });
var insertMethod = handlers.GetType().GetMethod("Insert");
insertMethod.Invoke(handlers, new object[] { index, handlerInfo });
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var textBox = new TextBox();
textBox.TextChanged += (o, e) => Console.WriteLine("External handler");
var b = new B(textBox);
textBox.Text = "foo";
b.MakeProblem();
}
}
class B
{
private TextBox _a;
bool _dontDoThis;
public B(TextBox a)
{
_a = a;
a.InsertEventHandler(0, TextBox.TextChangedEvent, new TextChangedEventHandler(Handler));
}
void Handler(object sender, TextChangedEventArgs e)
{
Console.WriteLine("B.Handler");
e.Handled = _dontDoThis;
if (_dontDoThis)
{
e.Handled = true;
return;
}
// do this!
}
public void MakeProblem()
{
try
{
_dontDoThis = true;
_a.Text = "make a problem";
}
finally
{
_dontDoThis = false;
}
}
}
B.Handler
External handler
B.Handler