3

我最近遇到了一个我不理解的行为。我有一个本地函数,它从封闭方法中捕获变量/参数。在这种情况下,在我看来,每次调用封闭方法时,都会创建本地函数的新“实例”。在下面的代码中很容易看到这种行为。

你能解释一下为什么本地函数会这样吗?

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;
    }

上面代码的一些替代方案是:

  • 如果你在局部函数内部创建一个局部变量并将参数分配给它,它的行为仍然像一个闭包。

  • 如果您创建一个字段并在封闭方法中为其分配参数,并在本地函数中访问该字段,它的行为就不会像一个闭包。

4

2 回答 2

1

编译器需要保存您的参数 (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);
    }
}

于 2020-03-06T17:08:21.443 回答
-2

这是因为你没有以正确的方式使用它。

通常,事件属于 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
    }
于 2020-03-03T10:42:25.393 回答