C# локальная функция с замыканием в качестве обработчика события: почему это работает так? - PullRequest
2 голосов
/ 03 марта 2020

Я недавно столкнулся с поведением, которое я не понимаю. У меня есть локальная функция, которая захватывает переменные / параметры из метода вложения. В этом случае мне кажется, что каждый раз, когда я вызываю включающий метод, будет создаваться новый «экземпляр» локальной функции. Это поведение легко увидеть в приведенном ниже коде.

Можете ли вы объяснить мне, почему локальная функция ведет себя так?

Ни VS, ни Resharper не дают мне никаких предупреждений за это, но это легко пропустить и может привести к трудностям поиска ошибок.

public class LocalFunctionTest
    {
        public static void Main(string[] args)
        {
            var localFunctionTest = new LocalFunctionTest();
            localFunctionTest.UnsubscribeSubscribe(1);
            localFunctionTest.UnsubscribeSubscribe(10);
            localFunctionTest.UnsubscribeSubscribe(100);
            Console.WriteLine(localFunctionTest.EventWithoutClosure?.GetInvocationList().Length ?? 0); //1
            Console.WriteLine(localFunctionTest.EventWithClosure?.GetInvocationList().Length ?? 0); //3
        }

        private void UnsubscribeSubscribe(int someParam)
        {
            void EventHandlerWithoutClosure(object sender, EventArgs args)
            {
            }

            //Local function that captures a variable/parameter
            void EventHandlerWithClosure(object sender, EventArgs args)
            {
                someParam++;
            }

            //Using local functions as event handlers
            EventWithoutClosure -= EventHandlerWithoutClosure;
            EventWithoutClosure += EventHandlerWithoutClosure;
            EventWithClosure -= EventHandlerWithClosure;
            EventWithClosure += EventHandlerWithClosure;
        }

        private event EventHandler EventWithoutClosure;
        private event EventHandler EventWithClosure;
    }

Некоторые альтернативы приведенному выше коду:

  • Если вы создаете локальную переменную внутри локальная функция и присвоение ей параметра все еще ведет себя как замыкание.

  • Если вы создаете поле и присваиваете ему параметр в методе включения, и получаете доступ к полю в локальная функция, она не будет вести себя как замыкание.

Ответы [ 2 ]

1 голос
/ 06 марта 2020

Компилятору необходимо сохранить значение вашего параметра (someParam), потому что вы увеличиваете его позже в своей локальной функции. Так что в этом случае он не может использовать синглтон.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Threading;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
public class LocalFunctionTest
{
    [CompilerGenerated]
    private sealed class <>c__DisplayClass1_0
    {
        public int someParam;

        private void <UnsubscribeSubscribe>g__EventHandlerWithClosure|1(object sender, EventArgs args)
        {
            someParam++;
        }
    }

    [Serializable]
    [CompilerGenerated]
    private sealed class <>c
    {
        public static readonly <>c <>9 = new <>c();

        private void <UnsubscribeSubscribe>g__EventHandlerWithoutClosure|1_0(object sender, EventArgs args)
        {
        }
    }

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private EventHandler m_EventWithoutClosure;

    [CompilerGenerated]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private EventHandler m_EventWithClosure;

    private event EventHandler EventWithoutClosure
    {
        [CompilerGenerated]
        add
        {
            EventHandler eventHandler = this.m_EventWithoutClosure;
            EventHandler eventHandler2;
            do
            {
                eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Combine(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.m_EventWithoutClosure, value2, eventHandler2);
            }
            while ((object)eventHandler != eventHandler2);
        }
        [CompilerGenerated]
        remove
        {
            EventHandler eventHandler = this.m_EventWithoutClosure;
            EventHandler eventHandler2;
            do
            {
                eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Remove(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.m_EventWithoutClosure, value2, eventHandler2);
            }
            while ((object)eventHandler != eventHandler2);
        }
    }

    private event EventHandler EventWithClosure
    {
        [CompilerGenerated]
        add
        {
            EventHandler eventHandler = this.m_EventWithClosure;
            EventHandler eventHandler2;
            do
            {
                eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Combine(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.m_EventWithClosure, value2, eventHandler2);
            }
            while ((object)eventHandler != eventHandler2);
        }
        [CompilerGenerated]
        remove
        {
            EventHandler eventHandler = this.m_EventWithClosure;
            EventHandler eventHandler2;
            do
            {
                eventHandler2 = eventHandler;
                EventHandler value2 = (EventHandler)Delegate.Remove(eventHandler2, value);
                eventHandler = Interlocked.CompareExchange(ref this.m_EventWithClosure, value2, eventHandler2);
            }
            while ((object)eventHandler != eventHandler2);
        }
    }

    public static void Main(string[] args)
    {
        LocalFunctionTest localFunctionTest = new LocalFunctionTest();
        localFunctionTest.UnsubscribeSubscribe(1);
        localFunctionTest.UnsubscribeSubscribe(10);
        localFunctionTest.UnsubscribeSubscribe(100);
        EventHandler eventWithoutClosure = localFunctionTest.m_EventWithoutClosure;
        Console.WriteLine((eventWithoutClosure != null) ? eventWithoutClosure.GetInvocationList().Length : 0);
        EventHandler eventWithClosure = localFunctionTest.m_EventWithClosure;
        Console.WriteLine((eventWithClosure != null) ? eventWithClosure.GetInvocationList().Length : 0);
    }

    private void UnsubscribeSubscribe(int someParam)
    {
        <>c__DisplayClass1_0 <>c__DisplayClass1_ = new <>c__DisplayClass1_0();
        <>c__DisplayClass1_.someParam = someParam;
        EventWithoutClosure -= new EventHandler(<>c.<>9.<UnsubscribeSubscribe>g__EventHandlerWithoutClosure|1_0);
        EventWithoutClosure += new EventHandler(<>c.<>9.<UnsubscribeSubscribe>g__EventHandlerWithoutClosure|1_0);
        EventWithClosure -= new EventHandler(<>c__DisplayClass1_.<UnsubscribeSubscribe>g__EventHandlerWithClosure|1);
        EventWithClosure += new EventHandler(<>c__DisplayClass1_.<UnsubscribeSubscribe>g__EventHandlerWithClosure|1);
    }
}

0 голосов
/ 03 марта 2020

Это потому, что вы не используете его правильно.

Обычно событие относится к 1 классу, и обработчики регистрируются из других классов (экземпляров). Если вы используете локальную функцию, почему бы вам просто не вызвать код напрямую без использования обработчика событий?

https://docs.microsoft.com/en-us/dotnet/api/system.eventhandler-1?view=netcore-3.1

Если вы все еще настаиваете на этом, Вы должны сделать

    public static void Main(string[] args)
    {
        var localFunctionTest = new LocalFunctionTest();
        //localFunctionTest.UnsubscribeSubscribe(1);
        //localFunctionTest.UnsubscribeSubscribe(10);
        //localFunctionTest.UnsubscribeSubscribe(100);

        localFunctionTest.EventWithoutClosure += (object sender, EventArgs args) =>
        {
            var test = 1; // dosomething;
        };
        localFunctionTest.EventWithClosure += (object sender, EventArgs args) =>
        {
            var test = 1; // dosomething;
        };

        Console.WriteLine(localFunctionTest.EventWithoutClosure?.GetInvocationList().Length ?? 0); //1
        Console.WriteLine(localFunctionTest.EventWithClosure?.GetInvocationList().Length ?? 0); //1
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...