gpt4 book ai didi

c# - C#-事件订阅和变量覆盖

转载 作者:行者123 更新时间:2023-11-30 15:59:05 24 4
gpt4 key购买 nike

我一直在摆弄静态事件,并对一些事情感到好奇。

这是我正在使用和修改这些问题的基本代码。

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



更改代码并删除退订时,Console.WriteLine输出仍然相同。为什么会发生这种情况?为什么这样不好?


    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



在更改代码并同时删除取消订阅和重新订阅时,Console.WriteLine输出是不同的。为什么输出不是 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

最佳答案

更改代码并删除退订时,Console.WriteLine输出仍然相同。为什么会发生这种情况?为什么这样不好?
  


C#委托实际上是“多播”委托。也就是说,单个委托实例可以具有多个调用目标。但是,当委托具有返回值时,只能使用一个值。在您的示例中,恰好由于委托订购的排序方式,如果删除第一个取消订阅操作,则是该事件的第二个委托,其返回值由事件的调用返回。

因此,在该特定示例中,取消订阅该事件的第一个委托对返回的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。关于您对第1季度的回答,我更想知道它在变量处置方面是否不好。正如q2似乎暗示的那样,即使将 col设置为新实例,也不会删除初始的 col(及其预订)。这最终会导致内存泄漏,还是gc会捡起来?


我不清楚您所说的“可变处置”是什么意思。从通常的意义上讲,变量本身实际上并不是“配置”的。因此,我推断您实际上是在谈论垃圾回收。考虑到这种推论...

答案是,如果您不退订引用原始对象的原始委托,则不会收集原始对象。有些人确实使用“内存泄漏”一词来描述这种情况(我没有,因为这样做无法将这种情况与其他类型的内存管理方案中可能发生的实际内存泄漏区分开来,在这种情况下,为对象确实永久丢失)。

在.NET中,当对象不再可访问时,可以进行垃圾回收。何时实际收集该对象取决于GC。通常,我们只关注资格,而不关注实际收藏。

对于最初由 col变量引用的对象,只要该局部变量仍在范围内并且仍可以在该方法中使用,则可以访问该对象。一旦变量引用的对象用于预订事件,事件本身现在还可以通过已预订的委托来引用该对象(显然,否则,委托将如何传递正确的 this调用处理事件的实例方法时的值?)。

如果您不从事件的订阅者中删除该委托(对原始对象的引用),则该对象本身仍然可以访问,因此不符合垃圾回收的条件。

对于不是类的 static成员的事件,这通常不是问题,因为只要对象本身存在,通常就希望保留对该事件的订阅。当对象本身不再可访问时,任何已订阅其事件的事件处理对象也将到达。

就您而言,您正在处理 static事件。实际上,这确实可能是内存泄漏的潜在原因,因为类的 static成员始终可以访问。因此,在取消订阅引用创建的原始对象的委托之前,该原始对象也将保持可访问状态并且无法被收集。


  2a。对于q2,仅更改 strings属性本身而不是完全替换 col是否更有意义?不能完全确定原因,但是您的回答使我想到了这一点。代码: col.strings = new [] { "b", "b"};


没有更多的上下文,我不能说什么将“更有意义”。但是,如果这样做的话,您的代码确实会在所有四种情况下都产生预期的结果(即您是否在两个示例中都注释掉了事件订阅和-unsubscription代码)。并且,通过避免分配新对象,您可以避免整个问题,即意外地未能从事件中取消订阅对象的处理程序,或者使该对象意外地保持可访问状态。

关于c# - C#-事件订阅和变量覆盖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42127934/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com