我一直在摆弄静态事件,并对一些事情感到好奇......
这是我正在使用的基本代码并改变了这些问题。
class Program
{
static void Main()
{
aa.collection col = null;
col = new aa.collection(new [] { "a", "a"});
aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
// Used in question 1
aa.evGatherstringa -= col.gatherstring;
col = new aa.collection(new [] { "b", "b"});
// Used in question 2
aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
}
public static class aa
{
public delegate string gatherstringa();
public static event gatherstringa evGatherstringa;
public static string gatherstring() { return evGatherstringa.Invoke(); }
public class collection
{
public collection(string[] strings) { this.strings = strings; }
public string gatherstring()
{
return this.strings[0];
}
public string[] strings { get; set; }
}
}
}
输出:
a
b
static void Main()
{
aa.collection col = null;
col = new aa.collection(new [] { "a", "a"});
aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
// Used in question 1
//aa.evGatherstringa -= col.gatherstring;
col = new aa.collection(new [] { "b", "b"});
// Used in question 2
aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
}
输出:
a
b
a
,而是b
? static void Main()
{
aa.collection col = null;
col = new aa.collection(new [] { "a", "a"});
aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
// Used in question 1 and 2
//aa.evGatherstringa -= col.gatherstring;
col = new aa.collection(new [] { "b", "b"});
// Used in question 2
//aa.evGatherstringa += col.gatherstring;
Console.WriteLine(aa.gatherstring());
}
输出:
a
a
答案 0 :(得分:3)
- 更改代码并删除取消订阅时,Console.WriteLine输出仍然相同。为什么会这样?为什么这么糟糕?
醇>
C#委托实际上是一个"多播"代表。也就是说,单个委托实例可以具有多个调用目标。但是当委托具有返回值时,只能使用一个值。在您的示例中,恰好由于委托订阅的排序方式,如果您删除第一个取消订阅操作,它是订阅事件的第二个委托,其返回值由事件&返回#39; s调用。
因此,在该特定示例中,取消订阅事件中的第一个委托对返回的string
值没有影响。您仍然可以从第二个委托实例返回string
值,即使正在调用这两个委托。
至于"为什么这么糟糕?",好吧......是吗?是否取决于具体情况。我想说,这是一个很好的例子,说明为什么你应该避免使用除void
返回类型以外的委托类型的事件。至少可以说有多个返回值,但只能看到调用中实际返回的值之一,这可能会令人困惑。
至少,如果您确实为事件使用了这样的委托类型,您应该愿意接受默认行为或将多播委托实例分解为其各个调用目标(请参阅Delegate.GetInvocationList()
)并明确决定你想要的回报值。
如果您确实知道自己正在做什么,并且熟悉多播委托的工作方式,并且对丢失除一个返回值之外的所有内容(或明确捕获代码中的所有返回值)感到满意举起活动),然后我不会说它必然是坏的"本身。但它绝对是非标准的,如果不小心完成,几乎可以肯定意味着代码没有按预期工作。哪个 坏。 :)
- 更改代码并同时删除取消订阅和重新订阅时,Console.WriteLine输出会有所不同。为什么输出a不是b?
醇>
您期望这样,因为您已修改了col
变量,以前订阅的事件处理程序会以某种方式自动引用分配给col
变量的新实例。但这不是事件订阅的工作方式。
当您第一次使用aa.evGatherstringa += col.gatherstring;
订阅该活动时,col
变量仅用于提供对aa.collection
的实例的引用找到事件处理程序方法的位置。事件订阅仅使用该实例引用。事件订阅不会观察到变量本身,因此稍后对变量的更改也不会影响事件订阅。
相反,aa.collection
对象的原始实例仍然订阅了该事件。即使在您修改了col
变量之后,再次引发事件仍然会调用该原始对象中的事件处理程序,而不是现在分配给col
变量的新对象。
更一般地说,您要非常小心,不要将实际对象与可以存储在各种位置的引用混淆,并将任何单个变量存储在该引用中。
如果您有以下代码,原因相同:
aa.collection c1, c2;
c1 = new aa.collection(new [] { "a" });
c2 = c1;
c1 = new aa.collection(new [] { "b" });
...即使您已为变量c2
分配了新值,c1
的值也不会更改。您只需通过重新分配c1
来更改变量值。原始对象引用仍然存在,并保留在变量c2
。
的附录:强>
解决评论中发布的两个后续问题......
1a上。关于你的q1响应,我更好奇的是它在变量处理方面是不好的。正如q2似乎暗示的那样,即使将
col
设置为新实例,也不会删除初始col
(及其订阅)。这会最终导致内存泄漏,还是gc会把它拿起来?
我不清楚"变量处理" 的含义。在任何通常的意义上,变量本身实际上并没有被处理掉。所以,我推断你真的在谈论垃圾收集。考虑到这种推断......
答案是,如果您没有取消订阅引用原始对象的原始代表,则不会收集原始对象。有些人确实使用术语"内存泄漏" 来描述这种情况(我不这样做,因为这样做无法将情况与可能发生的实际内存泄漏区分开来)其他类型的内存管理方案,其中为对象分配的内存真正永久丢失。
在.NET中,当一个对象不再可访问时,该对象符合条件进行垃圾回收。 当实际收集该对象时,由GC决定。通常情况下,我们只关注自己的资格,而不是实际的收藏。
对于最初由col
变量引用的对象,只要该局部变量仍在范围内且仍可在该方法中使用,则它是可到达的。一旦变量引用的对象用于订阅事件,事件本身现在也通过订阅的委托引用该对象(显然......否则,委托如何能够传递正确的{{1}调用处理事件的实例方法时的值?)。
如果您没有从事件的订阅者那里删除该委托及其对原始对象的引用,那么该对象本身仍然可以访问,因此不可以进行垃圾回收。
如果事件是某个类的非this
成员,这通常不是问题,因为只要对象本身存在,人们通常希望保持订阅该事件。当对象本身不再可达时,任何事件处理订阅其事件的对象也是如此。
在您的情况下,您正在处理static
事件。这确实可能是内存泄漏的潜在来源,因为类的static
成员总是可达。因此,在您取消订阅引用创建的原始对象的委托之前,该原始对象也仍然可以访问,并且无法收集。
2a上。至于q2,简单地更改
static
属性本身而不是完全替换strings
会更有意义吗?不完全确定原因,但你的反应让人想到了这一点。代码:col
如果没有更多背景信息,我无法说出"更有意义" 。但是,如果您这样做,您的代码确实会在所有四种情况下产生预期结果(即,您是否已在两个示例中注释了事件订阅和-unsubscription代码)。并且通过避免分配新对象,您可以在整个问题上采取措辞,即无意中从事件中取消订阅对象的处理程序,或者无意中使该对象无法访问。