更新:为了阅读本文的任何人的利益,自 .NET 4 以来,由于自动生成事件的同步发生变化,因此不需要锁定,所以我现在只使用它:
public static void Raise<T>(this EventHandler<T> handler, object sender, T e) where T : EventArgs
{
if (handler != null)
{
handler(sender, e);
}
}
并提高它:
SomeEvent.Raise(this, new FooEventArgs());
一直在阅读 Jon Skeet 的一本书 articles on multithreading ,我试图封装他提倡的在扩展方法中引发事件的方法(具有类似的通用版本):
public static void Raise(this EventHandler handler, object @lock, object sender, EventArgs e)
{
EventHandler handlerCopy;
lock (@lock)
{
handlerCopy = handler;
}
if (handlerCopy != null)
{
handlerCopy(sender, e);
}
}
然后可以这样调用:
protected virtual void OnSomeEvent(EventArgs e)
{
this.someEvent.Raise(this.eventLock, this, e);
}
这样做有什么问题吗?
此外,我对锁的必要性一开始有点困惑。据我了解,在文章的示例中复制了委托(delegate),以避免它在 null 检查和委托(delegate)调用之间发生变化(并变为 null)的可能性。但是,我的印象是这种访问/分配是原子的,那么为什么需要锁呢?
更新:关于下面马克·辛普森的评论,我做了一个测试:
static class Program
{
private static Action foo;
private static Action bar;
private static Action test;
static void Main(string[] args)
{
foo = () => Console.WriteLine("Foo");
bar = () => Console.WriteLine("Bar");
test += foo;
test += bar;
test.Test();
Console.ReadKey(true);
}
public static void Test(this Action action)
{
action();
test -= foo;
Console.WriteLine();
action();
}
}
这个输出:
Foo
Bar
Foo
Bar
这说明方法的委托(delegate)参数 (action) 没有反射(reflect)传递给它的参数 (test),这是意料之中的,我猜测。我的问题是这会影响我的 Raise 扩展方法上下文中锁的有效性吗?
更新:这是我现在使用的代码。它不像我希望的那样优雅,但它似乎有效:
public static void Raise<T>(this object sender, ref EventHandler<T> handler, object eventLock, T e) where T : EventArgs
{
EventHandler<T> copy;
lock (eventLock)
{
copy = handler;
}
if (copy != null)
{
copy(sender, e);
}
}
最佳答案
锁的目的是在覆盖默认事件连接时保持线程安全。如果其中一些解释了您已经能够从 Jon 的文章中推断出的事情,我们深表歉意;我只是想确保我对所有事情都完全清楚。
如果你这样声明你的事件:
public event EventHandler Click;
然后事件订阅会自动与 lock(this) 同步。您不需要编写任何特殊的锁定代码来调用事件处理程序。完全可以这样写:
var clickHandler = Click;
if (clickHandler != null)
{
clickHandler(this, e);
}
但是,如果您决定覆盖默认事件,即:
public event EventHandler Click
{
add { click += value; }
remove { click -= value; }
}
现在您遇到了问题,因为不再有隐式锁。您的事件处理程序刚刚失去了线程安全性。这就是你需要使用锁的原因:
public event EventHandler Click
{
add
{
lock (someLock) // Normally generated as lock (this)
{
_click += value;
}
}
remove
{
lock (someLock)
{
_click -= value;
}
}
}
就我个人而言,我对此并不在意,但 Jon 的理由很充分。但是,我们确实有一个小问题。如果您使用私有(private) EventHandler 字段来存储您的事件,您可能在类内部有执行此操作的代码:
protected virtual void OnClick(EventArgs e)
{
EventHandler handler = _click;
if (handler != null)
{
handler(this, e);
}
}
这很糟糕,因为我们正在访问相同的私有(private)存储字段,而没有使用属性使用的相同锁。
如果类外部的一些代码:
MyControl.Click += MyClickHandler;
通过公共(public)属性的外部代码正在兑现锁。但你不是,因为你接触的是私有(private)领域。
clickHandler = _click 的变量赋值部分是原子的,是的,但是在该赋值期间,_click 字段可能处于 transient 状态,由外部类编写了一半。当您同步对字段的访问时,仅同步写访问是不够的,您还必须同步读访问:
protected virtual void OnClick(EventArgs e)
{
EventHandler handler;
lock (someLock)
{
handler = _click;
}
if (handler != null)
{
handler(this, e);
}
}
更新
事实证明,评论周围的一些对话实际上是正确的,OP 的更新证明了这一点。这不是扩展方法本身的问题,而是委托(delegate)具有值类型语义并在赋值时被复制的事实。即使您从扩展方法中取出 this 并将其作为静态方法调用,您也会得到相同的行为。
您可以使用静态实用方法来绕过此限制(或功能,取决于您的观点),尽管我很确定您不能使用扩展方法。这是一个有效的静态方法:
public static void RaiseEvent(ref EventHandler handler, object sync,
object sender, EventArgs e)
{
EventHandler handlerCopy;
lock (sync)
{
handlerCopy = handler;
}
if (handlerCopy != null)
{
handlerCopy(sender, e);
}
}
此版本有效,因为我们实际上并未传递 EventHandler,只是对其的引用(请注意方法签名中的 ref ).不幸的是,您不能在扩展方法中将 ref 与 this 一起使用,因此它必须保留为纯静态方法。
(如前所述,您必须确保传递与在公共(public)事件中使用的 sync 参数相同的锁定对象;如果传递任何其他对象,则整个讨论没有实际意义。)
关于c# - 这是在 C# 中引发事件的有效模式吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2123608/
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
这个问题在这里已经有了答案:Arraysmisbehaving(1个回答)关闭6年前。是否应该这样,即我误解了,还是错误?a=Array.new(3,Array.new(3))a[1].fill('g')=>[["g","g","g"],["g","g","g"],["g","g","g"]]它不应该导致:=>[[nil,nil,nil],["g","g","g"],[nil,nil,nil]]
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
我在rspec中收到来自webkit驱动程序的以下消息:Capybara::Driver::Webkit::WebkitInvalidResponseError:UnabletoloadURL:http://127.0.0.1:44923/posts几天前它成功了。问题出在save_page方法上。有什么问题吗? 最佳答案 当我的页面出现错误时,我收到过类似的错误消息。您应该通过在测试模式下启动服务器(railss-etest)并自行访问页面来手动检查情况是否如此。 关于ruby-on-
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s