28

EDIT: This is not a duplicate of this question as this one is a practical example working with Delegate.CreateDelegate and the other one is a theoretical discussion about IL. Nothing to do one with each other besides the words this and null.

Relative to this question ...

I have a situation when an event handler is called on an instance that is null. Weird. Look at the image:

enter image description here

I do not understand what is happening. How an instance method can be called on a null instance???

Community
  • 1
  • 1
Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • maybe the method is from an interface being implemented by that class? – pythonian29033 Jul 16 '13 at 11:49
  • 3
    Because it already has been garbage collected? You probably forgot to deregister your event handler after usage. – Marcel Jul 16 '13 at 11:50
  • perhaps the object the handler belongs to was disposed, but the handler is still attached to some event somewhere? – mao47 Jul 16 '13 at 11:50
  • 3
    @Marcel: as far as I know if the event handler is registered then it cannot be garbage collected as there is a reference to the object. – Ignacio Soler Garcia Jul 16 '13 at 11:50
  • @mao47: no IDisposable here ... – Ignacio Soler Garcia Jul 16 '13 at 11:51
  • 2
    @MichaelPerrenoud: no dup, I already checked that. This is a practical question while the other is just a discussion about theoretical IL. – Ignacio Soler Garcia Jul 16 '13 at 11:52
  • @pythonian29033: even in that case I would expect to hace the instance of the class implementing the interface ... – Ignacio Soler Garcia Jul 16 '13 at 11:52
  • 2
    Probably not the case here, but note that it's _actually [possible](http://stackoverflow.com/questions/951624/how-to-create-a-delegate-to-an-instance-method-with-a-null-target)_ to create a delegate to call an instance method where `this` is `null`. – Chris Sinclair Jul 16 '13 at 11:52
  • 2
    @ChrisSinclair: you're absolutely right. Can you post your answer as an answer and I will accept it. I was using code to create a generic delegate and the target was set to null. God bless you ;) – Ignacio Soler Garcia Jul 16 '13 at 11:55
  • @SoMos: sorry, I meant what Marcel said. Looks like you figured it out. – mao47 Jul 16 '13 at 12:01
  • Also, you can do it by emitting the IL OpCodes manually: http://stackoverflow.com/questions/11162652/c-sharp-get-property-value-without-creating-instance – Chris Sinclair Jul 16 '13 at 12:34

1 Answers1

35

You can create this case using the Delegate.CreateDelegate overload where you provide a null reference for the target of invocation.

class Foo
{
    public void Method() 
    {
        Console.WriteLine(this == null);
    }
}

Action<Foo> action = (Action<Foo>)Delegate.CreateDelegate(
    typeof(Action<Foo>), 
    null, 
    typeof(Foo).GetMethod("Method"));

action(null); //prints True

From the MSDN remarks on that page:

If firstArgument is a null reference and method is an instance method, the result depends on the signatures of the delegate type type and of method:

•If the signature of type explicitly includes the hidden first parameter of method, the delegate is said to represent an open instance method. When the delegate is invoked, the first argument in the argument list is passed to the hidden instance parameter of method.

•If the signatures of method and type match (that is, all parameter types are compatible), then the delegate is said to be closed over a null reference. Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do.

So it's documented as a known, and probably intended, behaviour.

Chris Sinclair
  • 22,858
  • 3
  • 52
  • 93
  • Absolutely good, this is what I was doing using someone else's code ... weird. – Ignacio Soler Garcia Jul 16 '13 at 12:00
  • 3
    I think it is very weird that this is allowed. I would have expected for `Delegate.CreateDelegate` to throw an exception in this scenario. I can't think of a single use case where you would want to call an instance method without an instance... – Daniel Hilgarth Jul 16 '13 at 12:01
  • Can "this" be set to other object? Then it would be something similar to JavaScript bind/call/apply methods. – marisks Jul 16 '13 at 12:03
  • 1
    @DanielHilgarth , from [MSDN docs](http://msdn.microsoft.com/en-us/library/s3860fy3.aspx) `Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do.` So, I believe, null was just allowed for static methods. – default locale Jul 16 '13 at 12:04
  • Yes, scary indeed. I _suspect_ that the CLR doesn't necessarily care about having an instance available to _execute_ a method. In this case, the overload also handles static methods. I guess it could have checked the `MethodInfo.IsStatic` property in addition to checking if the target is null but I guess the Microsoft developers felt wasn't worthwhile (or had other implications)? – Chris Sinclair Jul 16 '13 at 12:04
  • @marisks: You can pass any `Foo` in, but I can't find a way (yet) to pass a non-Foo if that's what you were asking. – Chris Sinclair Jul 16 '13 at 12:06
  • @defaultlocale: The fact that it is documented to be allowed indicates that it was a conscious decision to allow it. Reflection for example doesn't allow it. If you try to call an instance method with `null` as the instance you will get an exception: "Non-static method requires a target.". I would have expected for `CreateDelegate` - or rather the created delegate - to behave the same. – Daniel Hilgarth Jul 16 '13 at 12:07
  • There is at least one other method of getting a `null` target for a delegate – when using a custom event that stores the delegates as weak references internally (and calling the callback after all other object instances have been collected). I can’t remember the context but I’ve heard this being used somewhere. – Konrad Rudolph Jul 16 '13 at 12:11
  • @KonradRudolph: Wouldn't the `Target` property of the delegate prevent collection of the instance? Or in other words: Shouldn't the instance be alive as long as the delegate? The delegate - even as a weak reference - should act as a local object root. – Daniel Hilgarth Jul 16 '13 at 12:13
  • What's _particularly_ fascinating, at least for me, is that if within this method you attempt to access any _field_ you'll throw a `NullReferenceException`. However, you can still call any _instance methods_ and not throw an exception (the methods will execute just like the first where `this == null` as though they were static) – Chris Sinclair Jul 16 '13 at 12:14
  • @ChrisSinclair: That is attributed to how instance methods are actually called by the CLR. If you look at the IL of an instance method call you see that it basically just uses `this` as the first value in the parameter stack. – Daniel Hilgarth Jul 16 '13 at 12:15
  • @Daniel No, that’s the whole point of `WeakReference`. The `Target` property does not cache its object, it dynamically determines whether the object is still alive when invoked. – Konrad Rudolph Jul 16 '13 at 12:17
  • Thanks Daniel. I figured as much, it's just so much... _fun_ to see something so _fundamental_ as `this != null` be changed. It's like seeing the laws of physics break down and investigating what does and doesn't work. One (useful?) application of this might be to call some method on a class that you can't instantiate: `class Foo { public Foo(){throw new Exception();} public void DoSomethingUseful() { } }` But we'd never be in a situation like that, right? >.> (EDIT: at least on non-CoreCLR platforms you can use `FormatterServices.GetUninitializedObject` instead) – Chris Sinclair Jul 16 '13 at 12:19
  • @DanielHilgarth Target is not required here to support open instance delegates. If delegate doesn't declare hidden "this" then it's officially useless (and documentation states that it's useless. – default locale Jul 16 '13 at 12:22
  • @KonradRudolph: I know. That's not my point. My point is that the objects *referenced* by the target of a weak reference should not be garbage collected before the target itself is garbage collected. Example: Consider a simple class `Foo` that has a property of type `List`. Now you create a weak reference for an instance of type `Foo`. If that instance wouldn't be a local root for the garbage collector, it could happen that the `List` is garbage collected while the `Foo` instance is not. The `Foo` instance would now "reference" a garbage collected instance. – Daniel Hilgarth Jul 16 '13 at 13:02
  • @Daniel Very right – what I meant was having a list of `WeakReference` delegates. That way not the handler but the delegate itself might be collected. Just to show what I was thinking of: http://ideone.com/HB6oGB – So the handler here is only called once (sometimes) even though we never remove it. The target is never `null` though, as you’ve correctly noted. – Konrad Rudolph Jul 16 '13 at 13:24
  • @KonradRudolph: Now we are talking about the same thing :-) Now that this is clarified, I fail to see how this scenario would result in a call to an instance method with `this` being `null` in any of the involved classes, which is what I was originally getting at. Extended sample: http://ideone.com/IHNnmv – Daniel Hilgarth Jul 16 '13 at 13:29
  • @Daniel Yes, that’s why I said that “The target is never `null` though, as you’ve correctly noted.” I was simply mistaken. – Konrad Rudolph Jul 16 '13 at 13:35
  • @KonradRudolph: Ah, ok :-) I thought I was still missing something. – Daniel Hilgarth Jul 16 '13 at 13:36
  • JonSkeet clarified the null target of a delegate is indeed for static methods, as per [here](http://stackoverflow.com/a/3447382/314291) – StuartLC Mar 20 '14 at 15:04
  • > Invoking the delegate is like calling an instance method on a null instance, which is not a particularly useful thing to do. This is actually pretty useful for implementing Virtual Static Methods in C#, because C# doesn't allow you to do something like `T.SomeStaticMethod()`. This way you can make a generic helper method that calls the appropriate instance method (or property) with a null `this`, and get the same behaviour as Virtual Static Methods + generics. – Ryan Dec 22 '21 at 04:29