https://codedefault.com/2014/csharp-winform-pass-value-between-forms-by-delegate-and-event
这个连接给出这种事件传值算是一种非常广泛的方式,
今天我想问题的,这种方式,如果那个窗体关闭了,前面那个窗体中的事件是不是没有解除,是不是泄露了

解决方案 »

  1.   

    caozhy快来看看哟
      

  2.   

    我的理解是事件是一种特殊的委托,而委托是将方法作为参数,事件在实际的执行中是循环执行委托链(一个委托的数组)。委托实际也是一个类,我认为在子窗体Close后,事件会被回收,因此不会出现泄露。
      

  3.   

    主窗体是客户,子窗体是服务,事件定义在子窗体,子窗体的定义并不依赖于主窗体,当子窗体被 Close 之后并不存在对子窗体对象的引用,因此子窗体可以被 GC 释放。你所谓的“前面那个窗体中的事件”这个描述是含糊的、错误的。事件定义在子窗体而不是主窗体,只不过事件触发时会调用主窗体的委托方法。但是这正是“依赖倒置”的本质!主窗体向子窗体注册委托方法,这正是“依赖倒置”,主窗体依赖子窗体而不是子窗体依赖主窗体,因此子窗体并不会产生泄露。反之,假设另外一种设计,假设子窗体向主窗体注册委托,子窗体依赖主窗体而主窗体并不依赖子窗体,那么直接关闭子窗体则会产生泄露,子窗体需要把委托从主窗体的事件注销掉,然后才关闭。
      

  4.   

    上面“当子窗体被 Close 之后并不存在对子窗体对象的引用”这个话还是对的,不需要修改。这个程序中,住程序使用代码var frmAddr = new frmAddress();来创建子窗体对象实例,是局部变量。所以主窗体在设计上是依赖子窗体的,但是是仅仅在局部方法内引用的。而子窗体使用事件方式,依赖倒置的解耦模式,根本不依赖于主窗体,可以作为通用的服务窗口提供给各种各样的宿主使用。整个过程中主窗体并没有持久地引用子窗体,所以子窗体是可以随时被 GC 回收的。
      

  5.   

           我上面的观点是错误的,十分抱歉,之前没有进行验证。不管子窗体Close和Dispose后,子窗体事件都是可以触发的。
           我写了一个Demo,才发现子窗体Close、Dispose只是释放不同的资源,哪怕是置空null和执行多次GC.Collect()后依旧如此,也并没有执行窗体析构函数并销毁对象。
           没找到官方的文档说明,可能也只有进程被销毁时,才会销毁窗体的对象吧,关于这一点我也不是很清楚。        public MainForm()
            {
                InitializeComponent();
            }        private void button1_Click(object sender, EventArgs e)
            {
                ChildForm child = new ChildForm();
                //child.eventTest += eventTest;//订不订阅都一样
                child.ShowDialog();
                child.Close();
                child.Dispose();            triggerGC();            child.TriggerEvent();            child = null;            triggerGC();
            }        private void eventTest(string str)
            {
                this.Invoke((EventHandler)(delegate
                {
                    this.label1.Text = str;
                }));
            }        private void triggerGC()
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();            Thread.Sleep(500);
            }
      

  6.   

    子窗体的代码,不知为什么没显示出来。        ~ChildForm()
            {
                MessageBox.Show("Collect");
            }        public ChildForm()
            {
                InitializeComponent();
            }        public delegate void EventTest(string str);
            public event EventTest eventTest;        private void button1_Click(object sender, EventArgs e)
            {
                TriggerEvent();
            }        public void TriggerEvent()
            {
                if (eventTest != null)
                {
                    eventTest(new Random().Next().ToString());
                }
            }
      

  7.   


    基础,如果窗体A发起事件,那么结果要在窗体B中执行,窗体B的执行结果与窗体A无关;如果窗体B结束以后,窗体A再发起事件,无窗体执行,函数不会重入,没有内存泄漏,只是窗体A的内存没有释放
      

  8.   

    确实,我之前做项目的时候也有这个疑问,我用WPF不过原理应该差不多,就是在子窗体Close的时候,子窗体的析构函数并没有被调用,无论开关多少次都是,最后在主窗体Close的时候会调用之前创建的所有子窗体的析构
      

  9.   

    没那么复杂的dim fm as new 弹出的Form with {.text =??? }
    fm.show
    if fm. DialogResult =ok then
    textbox1.text = fm.textbox1.text
    endif不就完了吗?何必事件来事件去的。
      

  10.   

    Dispose是Form class内部的事件,无法重载,他做的是一些基础的事。看两张图就可以知道,在窗体的Dispose之前,一些窗体的基础属性是可见的,Dispose将回收这些部分的数据。
    但是你对窗体附加的公有属性在你的窗体变量消失前都不会被回收。所以这和传值关系不大,只是一个一般的生命周期问题。另外说一下窗体基于GDI和GDI+,所以你怕泄露可以像QQ一样用dxui或者用wpf,否则泄露根本用不着看内存和窗体内生命周期的…………
      

  11.   


    using System.Threading.Tasks;
    using System.Windows.Forms;namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }        public string test { get; set; }        private void Button1_Click(object sender, EventArgs e)
            {
                Form1 child = new Form1();
                child.test = "aaaaaa";
                child.ShowDialog();
                child.Dispose();
                string test = child.test;
                string testcap = child.Text;
            }        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
            {
                this.Dispose();
            }    }
    }
    贴一下窗体源代码,窗体设计器我就不贴了,就一个按钮。项目也就一个窗体项目,就一个窗体。
      

  12.   

    在子窗体Dispose之后,事件中的数组(委托数组)是没有被回收的,主窗体去调用方法触发事件时,主窗体的Label的值依旧会改变,那它属不属于泄漏呢?
      

  13.   

    确实,我之前做项目的时候也有这个疑问,我用WPF不过原理应该差不多,就是在子窗体Close的时候,子窗体的析构函数并没有被调用,无论开关多少次都是,最后在主窗体Close的时候会调用之前创建的所有子窗体的析构
    我调试了一下.net framework源代码,发现窗体调用Close和Dispose时,会调用基类Componenet.cs中Dispose方法中,该方法中存在GC.SuppressFinalize(this)从而导致了不会再触发窗体析构函数的。        /// <devdoc>
            ///    <para>
            ///       Disposes of the <see cref='System.ComponentModel.Component'/>
            ///       .
            ///    </para>
            /// </devdoc>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed")]
            public void Dispose() {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
      

  14.   

    在子窗体Dispose之后,事件中的数组(委托数组)是没有被回收的,主窗体去调用方法触发事件时,主窗体的Label的值依旧会改变,那它属不属于泄漏呢?我不明白你说的泄露是指什么,你自己绑定过去的事件,你自己dispose子窗体后你自己应该解绑啊,你不解绑程序当然不能帮你解绑,如果你后面还需要这个窗体做其他操作呢?这不是vs的问题,dispose本身就只能回收窗体基础资源,你自己创建的多余资源本身就应该你自己回收,上面的例子讲的不是很明白吗?
      

  15.   

    在子窗体Dispose之后,事件中的数组(委托数组)是没有被回收的,主窗体去调用方法触发事件时,主窗体的Label的值依旧会改变,那它属不属于泄漏呢?我不明白你说的泄露是指什么,你自己绑定过去的事件,你自己dispose子窗体后你自己应该解绑啊,你不解绑程序当然不能帮你解绑,如果你后面还需要这个窗体做其他操作呢?这不是vs的问题,dispose本身就只能回收窗体基础资源,你自己创建的多余资源本身就应该你自己回收,上面的例子讲的不是很明白吗?
    题目问的就是子窗体自定义的事件没有解绑会不会导致泄漏呀,这是我没关系呀。
      

  16.   

    是的,就是这个理。

    我也相信二位说的是对的,这么说的我给的那个连接的例子就不会有内存泄露了对吧基础,如果窗体A发起事件,那么结果要在窗体B中执行,窗体B的执行结果与窗体A无关;如果窗体B结束以后,窗体A再发起事件,无窗体执行,函数不会重入,没有内存泄漏,只是窗体A的内存没有释放
    上面你说窗体A再发起事件,你这个窗体A本来就在,释放什么,你应该说的是窗体B没有释放吧?如果你是说的窗体B没有释放,那我想问B已经关了,为什么还不释放?
      

  17.   

    关闭--------------此2字和解难道指close这问题,让我想起了2个月前,有人问了一个一摸一样的问题他问题大概是,写了一个在一个init函数里写了一个事件挂接,然后问。此东西能触发不。最后一句也是“不接受反驳”难道和楼主是一个老师教的。同理,我现在说另一个东西。有个设备他回传消息,不接受回调注册,他要求必须是个form,必须在DefWndProce 窗体消息循环里处理那么问题来了。不“打开”能否接收消息呢?如果说楼主的“关闭”指的close,那么我们说“打开”指的是shownew form 不show,是否就不能接收到windows的消息泵呢?
      

  18.   

    我觉得你关注的点反了。
    .Net的事件机制无法保证在对象Dispose之后,所有该对象作为发布者被订阅的事件完全取消订阅。这个例子中子窗体是事件的发布者,父窗体是事件的订阅者。
    只要父窗体不再被持有任何引用,那么两者都可以进入GC的回收队列。
    如果父窗体是你的主窗体,那么你有主动去取消订阅的义务。举个不同的例子,假如你的窗体中有很多的控件,控件的事件被窗体本身订阅,
    当窗体不再被持有引用时,你就算没有取消这些事件订阅,窗体对象也可以正常被回收。
      

  19.   

    有关Dispose 并不是把form置为null,他只是等待GC去释放,而不是他现在就是null所以我们也会看到一种异常,那是“当某个消息事件发出来后,会产生一个无法访问已Dispose的对象”的异常
    对于pinvoke也会看到“某些托管程序异常,无法访问XXXX地址”--这个在pinvoke回调时尤其明显
      

  20.   

    wanghui0380  你是在扯蛋,我问释放,泄露的问题,谁叫你扯关闭了,我关闭点的窗体右上角的叉叉,你说指的什么?
    再这样瞎扯蛋,请勿复言
      

  21.   

    楼主可以查一下“ObjectDisposedException”的说明,为啥会有这种异常,原因就是楼主的问题。Dispose不等于对象为null
      

  22.   


    果然如此,谁在扯蛋,一目了然。原来“我关闭点的窗体右上角的叉叉”不等于form.close(),高见,高人啊!跪一个
      

  23.   


    可以明确告诉你,按你链接中的例子,子窗体不会被GC回收,存在泄漏的风险。
    因为子窗体的事件被一个外部对象(父窗体)订阅着,而这个外部对象还没进GC的回收队列。
    你可以自己去Dump验证
      

  24.   


    你拿我开心,也就算了,你还和wanghui0380抬杠,真的就过分了。
      

  25.   


    果然如此,谁在扯蛋,一目了然。原来“我关闭点的窗体右上角的叉叉”不等于form.close(),高见,高人啊!跪一个不用跑,这个人就是来捣乱的,昨天已经领教过他的那一套了。已经按照版规处理。请不要因此而影响您分享技术的热情。
      

  26.   


    在主窗体的生命周期结束前,他订阅的事件就不会释放,他订阅事件的窗体也就不会释放。
    gc本身就是一个依据生命周期回收的东西,哪怕你强制回收,也只能回收掉子窗体上那些已经过期的数据,还是无法回收他被订阅的事件。所以这个东西就会产生你强制回收,窗体上的元素变为null,然后订阅事件激活,窗体上的元素变为你需要激活的数值。解决方法也很简单,你关闭子窗体的时候取消订阅事件。
      

  27.   


    在主窗体的生命周期结束前,他订阅的事件就不会释放,他订阅事件的窗体也就不会释放。
    gc本身就是一个依据生命周期回收的东西,哪怕你强制回收,也只能回收掉子窗体上那些已经过期的数据,还是无法回收他被订阅的事件。所以这个东西就会产生你强制回收,窗体上的元素变为null,然后订阅事件激活,窗体上的元素变为你需要激活的数值。解决方法也很简单,你关闭子窗体的时候取消订阅事件。你又展开了一点,答案已经明确给了,能理解多少就看楼主自己的了。
      

  28.   


    在主窗体的生命周期结束前,他订阅的事件就不会释放,他订阅事件的窗体也就不会释放。
    gc本身就是一个依据生命周期回收的东西,哪怕你强制回收,也只能回收掉子窗体上那些已经过期的数据,还是无法回收他被订阅的事件。所以这个东西就会产生你强制回收,窗体上的元素变为null,然后订阅事件激活,窗体上的元素变为你需要激活的数值。解决方法也很简单,你关闭子窗体的时候取消订阅事件。你又展开了一点,答案已经明确给了,能理解多少就看楼主自己的了。
    你说的也许是对的,但是如果你仔细的看完所有的留言你会发现,他们的观点并不一至,我做为一个菜鸟,你叫我如何不纠结?
    反正我看了上面所有的留言我觉得他们的观点并
    不一致,
    不一致,
    不一致,
      

  29.   

    有些人喜欢回答问题时发散一点,有些人喜欢针对问题做解答。
    大家都是好意的,热心的这就够了。
    判断留给你自己做,相信你自己,但不要去针对别人,无论是工作中还是生活中。在主窗体的生命周期结束前,他订阅的事件就不会释放,他订阅事件的窗体也就不会释放。
    gc本身就是一个依据生命周期回收的东西,哪怕你强制回收,也只能回收掉子窗体上那些已经过期的数据,还是无法回收他被订阅的事件。所以这个东西就会产生你强制回收,窗体上的元素变为null,然后订阅事件激活,窗体上的元素变为你需要激活的数值。解决方法也很简单,你关闭子窗体的时候取消订阅事件。你又展开了一点,答案已经明确给了,能理解多少就看楼主自己的了。
    你说的也许是对的,但是如果你仔细的看完所有的留言你会发现,他们的观点并不一至,我做为一个菜鸟,你叫我如何不纠结?
    反正我看了上面所有的留言我觉得他们的观点并
    不一致,
    不一致,
    不一致,
      

  30.   

    先说得到的结论:子窗体关闭,不取消订阅事件并不会造成内存泄漏。@mingcsharp
    (1)首先说最最重要的一点是我找到MSDN关于取消事件的描述。画红线部分为只要发布者保持对事件的引用,GC就不会回收订阅的对象。这句话会不会意味着发布者不保持对事件的引用,GC就会回收订阅的对象呢,为了确保严肃性,我做了第二点的测试。(MSDN的链接:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/events/how-to-subscribe-to-and-unsubscribe-from-events)(2)结论是:无论主窗体有无订阅事件,子窗体关闭后,内存和对象的个数都是相同的,也就是不存在内存泄漏。(第一次打开子窗体后都会出现内存上涨,GC后也并未释放的情况,猜测.net framework可能对Form的对象做了特殊的处理吧,如果有知道的大佬也请解答一下)
    测试代码:
    //主窗体        public MainForm()
            {
                InitializeComponent();
            }        private void button1_Click(object sender, EventArgs e)
            {
                ChildForm child = new ChildForm();
                child.EventTest += eventTest;//测试不存在委托时,该行注释
                child.Show();
                child.TriggerEvent();//测试不存在委托时,该行注释
                child.Close();
            }        private void eventTest()
            {        }
    //子窗体        public ChildForm()
            {
                InitializeComponent();
            }        public delegate void DeEventTest();
            public event DeEventTest EventTest;        public void TriggerEvent()
            {
                if (EventTest != null)
                {
                    EventTest();
                }
            }
    内存探查结果(快照1均为原始状态,未进行任何操作;快照2及之后,点击了主窗体的按钮,打开并关闭了子窗体,并点击探查器上的强制GC按钮)
    原始参照:(主窗体未订阅事件,点击按钮仅打开了子窗体)
    主窗体订阅了事件,但关闭时并未取消该事件。
      

  31.   

    你得先搞清楚GC的工作原理:在执行GC时,会挂起全部线程,并将托管堆中所有的内存都打上垃圾的标记,之后遍历所有可到达的实例,这些实例如果引用了托管堆的内存,就将该内存的标记由垃圾变为被引用。
    当遇到A和B相互引用的时候,如果没有其他实例引用A或者B,虽然A和B相互引用,但是A和B都是不可到达的,即没办法引用A或者B,则A和B都会被判定为垃圾而被回收。讲解了这么一大堆,目的就是要说,在C#中,你想要释放一块内存,你只要让该块内存没有任何实例引用他,就可以了。以上是百度抄的***************************************************在你的代码里面的AddressUpdated引用的是AddressForm_ButtonClicked内存,这个内存和frmAddr 没关系,所以GC发现frmAddr 不会被引用,就会释放它。不会发生内存泄漏。一般事件中发生内存泄漏,都是因为使用了静态事件,静态事件的生存期很长,造成事件引用的所有内存块都可以被GC访问到,无法释放,内存块的所在类也就无法释放,导致内存泄漏。你这个代码没有使用静态事件,也没有使用ShowDialog,所以不会泄露的。
      

  32.   

    frmAddr 释放了,AddressForm_ButtonClicked也就没外人引用了,自然随着类的释放而释放