在.NET事件中使用ref参数时,PowerShell崩溃

Jon*_*ist 13 .net powershell events powershell-4.0

ref调用带有我订阅的参数的.NET事件时,PowerShell崩溃.

我已将问题减少到以下代码.

.净:

namespace PSEventTest
{
    public delegate void BadHandler(ref string str);

    public class PSEventTest
    {
        public event BadHandler BadEvent;

        public void InvokeBad()
        {
            var handler = BadEvent;
            if (handler != null)
            {
                var str = "bad";
                handler(ref str);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

电源外壳:

Add-Type -path $PSScriptRoot"\PSEventTest.dll"

$obj = New-Object PSEventTest.PSEventTest

$bad = Register-ObjectEvent -InputObject $obj -EventName BadEvent -Action {
    Write-Host "bad!" # it also crashes if this script-block is empty 
}

$obj.InvokeBad(); # crash
Run Code Online (Sandbox Code Playgroud)

当我运行PowerShell脚本时,我得到了这个:

在此输入图像描述

如果我按Debug我从Visual Studio得到这个:

在此输入图像描述

copy exception detail to the clipboard给我这个

System.NullReferenceException was unhandled
Message: An unhandled exception of type 'System.NullReferenceException' occurred in Unknown Module.
Additional information: Object reference not set to an instance of an object.
Run Code Online (Sandbox Code Playgroud)

我想我必须以-Action某种方式将字符串提供给脚本块,但我不知道如何.

PS我正在使用PowerShell 4:

PS C:\Windows\system32> $PSVersionTable.PSVersion
Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1
Run Code Online (Sandbox Code Playgroud)

Jon*_*ist 6

我找到了解决这个问题的方法:

.净:

namespace PSEventTest
{
    public delegate void BadHandler(ref string str);

    public class PSEventTest
    {
        public event BadHandler BadEvent;

        public string InvokeBad()
        {
            var handler = BadEvent;
            if (handler != null)
            {
                var str = "hi from .NET!";
                handler(ref str);
                return "from .NET: " + str;
            }
            return null;
        }
    }

    // as i mention below, this class can be implemented directly in the PS script
    public class BadEventSubscriber
    {
        public void Subscribe(PSEventTest legacyObj, Func<string, string> func)
        {
            legacyObj.BadEvent += delegate(ref string str)
            {
                var handler = func; 
                if (handler != null)
                {
                    var result = handler(str);
                    str = result;
                }
            };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

电源外壳:

Add-Type -path $PSScriptRoot"\PSEventTest.dll"

$legacyObj = New-Object PSEventTest.PSEventTest
$subscriber = New-Object PSEventTest.BadEventSubscriber

$subscriber.Subscribe($legacyObj, {
    param([string]$str)
    write-host "from PS: $str"
    "hi from PS!" 
})

write-host $legacyObj.InvokeBad();
Run Code Online (Sandbox Code Playgroud)

结果:

from PS: hi from .NET!
from .NET: hi from PS!
Run Code Online (Sandbox Code Playgroud)

以前我尝试创建一种(object sender, EventArgs e)处理程序来包装(ref string str)处理程序,但这不起作用,因为PS事件与.NET事件不同步.

传递Func代替使用事件处理程序似乎工作得更好,如上面的代码所示.

我试图概括BadEventSubscriber使用a Expression<Func<object, handler>>来选择Subscribe方法中传递的对象的事件,但是泛型并且Delegate不能很好地一起玩.所以你必须为BadEventSubsciber你想要订阅的每个类和处理程序创建一个-type类(PS不"支持"),但这对我来说是否足够好.

它没有真正的解决方案,因为你仍然需要额外的组件将事件"包装"成一个Func,但作为解决方案,我认为它是可以的.

编辑:

现在我考虑一下,你可以直接在PowerShell中定义订阅者类,就像@RomanKuzmin在我对原始问题的评论中所说的那样.因此,您只需在PowerShell中使用以下内容,而不是附加程序集:

Add-Type @"
    using System;

    public class BadEventSubscriber
    {
        public void Subscribe(PSEventTest.PSEventTest legacyObj, Func<string, string> func)
        {
            legacyObj.BadEvent += delegate(ref string str)
            {
                var handler = func; 
                if (handler != null)
                {
                    var result = handler(str);
                    str = result;
                }
            };
        }
    }
"@ -ReferencedAssemblies $PSScriptRoot"\PSEventTest.dll"

$subscriber = New-Object BadEventSubscriber
Run Code Online (Sandbox Code Playgroud)