简体   繁体   English

抛出 NullReferenceException 但 object 通过了 null 检查,这怎么可能?

[英]NullReferenceException thrown but the object passed the null check, how is that possible?

I'm using the AddEventHandler method from that answer , but when doing it on an EventHandler with a value type argument that happens:我正在使用该答案中的AddEventHandler方法,但是在带有值类型参数的EventHandler上执行此操作时:

using System.Reflection;

public class Program
{
    public static event EventHandler<bool> MyEvent;

    public static void Main()
    {
        EventInfo eventInfo = typeof(Program).GetEvent(nameof(MyEvent));
        AddEventHandler(eventInfo, null, (s, e) => {
            if (e == null) return; // either if condition or null conditional operator
            Console.WriteLine(e?.ToString());
        });
        MyEvent(null, true);
    }

    public static void AddEventHandler(EventInfo eventInfo, object client, EventHandler handler)
    {
        object eventInfoHandler = eventInfo.EventHandlerType
            .GetConstructor(new[] { typeof(object), typeof(IntPtr) })
            .Invoke(new[] { handler.Target, handler.Method.MethodHandle.GetFunctionPointer() });

        eventInfo.AddEventHandler(client, (Delegate)eventInfoHandler);
    }
}

在此处输入图像描述

Any explanation?有什么解释吗?

You are using undocumented, internal api, and what's even worse is that this api accepts raw pointer.您正在使用未记录的内部 api,更糟糕的是这个 api 接受原始指针。 So it's not surprising if things go (horribly) wrong if you misuse such api (and you cannot ever be sure you are using it correctly because it's not documented).因此,如果您滥用此类 go(可怕的)错误,这并不奇怪(并且您永远无法确定您是否正确使用它,因为它没有记录)。

Note that AddEventHandler third parameter is EventHandler , which is delegate of this type:请注意, AddEventHandler第三个参数是EventHandler ,它是这种类型的委托:

delegate void EventHandler(object sender, EventArgs e);

And your MyEvent delegate type is:你的MyEvent委托类型是:

delegate void EventHandler(object sender, int e);

AddEventHandler uses internal undocumented compiler-generated constructor of delegate which accepts two parameters: delegate target and raw pointer to method. AddEventHandler使用内部未记录的编译器生成的委托构造函数,它接受两个参数:委托目标和指向方法的原始指针 It then just passes raw pointer to the method of handler delegate to that constructor without doing any checks.然后它只是将指向handler委托方法的原始指针传递给该构造函数而不进行任何检查。 Delegate you pass and delegate being created can be completely incompatible, but you won't notice that until runtime will try to invoke it.您传递的委托和正在创建的委托可能完全不兼容,但您不会注意到这一点,直到运行时尝试调用它。

In this case, runtime thinks it has delegate pointing to method void (object, bool) , but actually it points to method void (object, EventArgs) .在这种情况下,运行时认为它有委托指向方法void (object, bool) ,但实际上它指向方法void (object, EventArgs) You call it via MyEvent(null, true) and runtime passes true boolean value as second argument (it's value type so value is passed directly), but your delegate actually points to method which expects EventArgs , which is a reference type.您通过MyEvent(null, true)调用它,运行时将true boolean 值作为第二个参数传递(它是值类型,因此直接传递值),但您的委托实际上指向期望EventArgs的方法,这是一个引用类型。 So it expects an address of some object of type EventArgs .所以它需要一个EventArgs类型的地址 object 。 It gets boolean value as if it was pointer to EventArgs .它获得 boolean 值,就好像它是指向EventArgs的指针一样。

Now, == null in general case basically just checks if the reference is zero (all bytes are 0).现在, == null在一般情况下基本上只是检查引用是否为零(所有字节均为 0)。 True boolean value is not represented by 0, so null check passes. True的 boolean 值没有用 0 表示,所以 null 检查通过。

Then, it tries to access object located at this "address".然后,它尝试访问位于该“地址”的 object。 It cannot work, you access protected memory and get access violation error.它无法工作,您访问受保护的 memory 并收到访问冲突错误。 However, as explained in this answer :但是,正如这个答案中所解释的那样:

but if (A) the access violation happened at an address lower than 0x00010000 and (B) such a violation is found to have happened by code that was jitted, then it is turned into a NullReferenceException, otherwise it gets turned into an AccessViolationException但是,如果 (A) 访问冲突发生在低于 0x00010000 的地址,并且 (B) 发现这种冲突是由被 jitted 的代码发生的,那么它会变成 NullReferenceException,否则会变成 AccessViolationException

So it is turned into NullReferenceException you observe.所以它变成了你观察到的NullReferenceException

Interesting that if you change your code like this:有趣的是,如果您像这样更改代码:

MyEvent(null, false);

Then it will run without errors, because false is represented by zero byte, and so e == null check will return true.然后它会运行没有错误,因为false由零字节表示,所以e == null check 将返回 true。

You can play with this code a bit more, for example change event type to int:您可以多玩一下这段代码,例如将事件类型更改为 int:

public static event EventHandler<int> MyEvent; 

And then do:然后做:

MyEvent(null, 0x00010001);

Now it will throw AccessViolationException instead of NullReferenceException as the linked answer claims (now we are trying to access memory at location higher than 0x00010000 so runtime does not convert this access violation into null reference exception).现在它将抛出AccessViolationException而不是NullReferenceException作为链接的答案声明(现在我们试图在高于 0x00010000 的位置访问 memory,因此运行时不会将此访问冲突转换为 null 引用异常)。

Here is another fun thing, we are using code from this answer to obtain memory address of .NET object at runtime, then pass that address into handler:这是另一个有趣的事情,我们使用这个答案中的代码在运行时获取 .NET object 的 memory 地址,然后将该地址传递给处理程序:

public static event EventHandler<IntPtr> MyEvent;

public static unsafe void Main() {
    // it's not even EventArgs, it's string
    var fakeArgument = "Hello world!";
    // some black magic to get address
    var typedRef = __makeref(fakeArgument);
    IntPtr ptr = **(IntPtr**)(&typedRef);
    EventInfo eventInfo = typeof(Program).GetEvent(nameof(MyEvent));
    AddEventHandler(eventInfo, null, (object s, EventArgs e) => {
        if (e == null) return;
        // e is actually a string here, not EventArgs...
        Console.WriteLine(e?.ToString());
    });
    MyEvent(null,  ptr);
}

This code outputs "Hello world,".此代码输出“Hello world”。 for the reasons explained above.出于上述原因。

So long story short - don't use such dangerous undocumented internal apis in real code.长话短说 - 不要在真实代码中使用这种危险的未记录的内部 api。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM