0

I've just stumbled upon a weird issue when trying to convert from anonymous function to a method group. The example below illustrates the issue perfectly - there are 2 methods - Run() and Crash() which do everything the same, except for using Anonymous Function vs Method Group.

Basically the issue is that I would like to create a Func to call a method on an object, which is not initialized on application start and instead comes later, BEFORE this func is ever run. Using an Anonymous Function works great, but converting to a Method Group causes an exception.

It's not really a big deal to stick to Anonymous Function instead of a Method Group, however a roslynator warning RCS1207 came up and I would like to understand the cause of this issue first, before simply disabling it with an inline comment.

namespace AnonymousFunctionVsmethodGroup
{
    class Program
    {
        static void Main(string[] args)
        {
            var app = new App();
            app.Run();
            app.Crash();
        }
    }

    public class App
    {
        private Func<string> m_Func;

        public void Run()
        {
            Entity cat = null;

            // Anonymous function. At this point cat is null
            m_Func = () => cat.GetName();

            // Initializing new cat
            cat = new Entity("Cat");

            // Func is executed on a valid cat
            Console.WriteLine(m_Func());
            Console.Read();
        }

        public void Crash()
        {
            Entity cat = null;

            // Method group. At this point cat is null. Code never gets through here and an exception is thrown instead.
            // "Delegate to an instance method cannot have null this"
            m_Func = cat.GetName;

            // Initializing new cat
            cat = new Entity("Cat");

            // Func is executed on a valid cat?
            Console.WriteLine(m_Func());
            Console.Read();
        }
    }

    // Sample entity
    public class Entity
    {
        private string name;

        public Entity(string name)
        {
            this.name = name;
        }

        public string GetName()
        {
            return name;
        }
    }
}
Kris Vandermotten
  • 10,111
  • 38
  • 49
Web Dev
  • 2,677
  • 2
  • 30
  • 41
  • 1
    You're actually attemping to use a particular (or lack thereof) `cat` instance in `Crash`. How do you not expect that to error? The lambda works because a lambda isn't executable code until it is invoked. Since you instantiate the instance before you actually invoke the lambda, then there is no error. If you swap those lines--initialization and invocation--then you should see the same error (NRE). You're probably getting an IDE warning on the usage in the lambda for a potential NRE. – Kenneth K. Jan 19 '18 at 23:46
  • Thanks for the explanation, however I am still confused. In the crash example - cat.GetName is assigned to a func, which I assume would not execute it until that func is called. Is that not correct? I understand that simply calling var myVar = cat.GetName() before that cat is initialized will cause a NRE. – Web Dev Jan 19 '18 at 23:50
  • I guess my question is simple - are the following 2 statements not identical : Func myFunc = () => myObject.DoStuff(); Func myFunc = myObject.DoStuff; – Web Dev Jan 19 '18 at 23:53
  • `GetName` isn't static, so it's tied to an instance, not the class itself. – Kenneth K. Jan 19 '18 at 23:58
  • 1
    Also, IIRC, the lambda is creating a closure around `cat`. This allows the lambda to have access to the updated reference once `cat` is instantiated. – Kenneth K. Jan 20 '18 at 00:02
  • 1
    @Ross they are not the same no – Dave Jan 20 '18 at 00:06

2 Answers2

1

() => GetName() is an lambda or annoymous function. The compiler does some clever stuff with this syntax and actually creates another method on the class you write it in (that's a huge over simplification but it will do for now, check out closures for more info). The code in the lambda is moved into this new method and is only called when you evaluate the lambda, ie when you invoke your func. Whereas using cat.GetName is passing in a method that belongs to an instance of cat which is null so you can't access it at that point

Dave
  • 2,829
  • 3
  • 17
  • 44
1

When you assign a "Method Group", the delegate associated with the object will be called immediately. However, the lambda creates an additional redirection: The code will first call store an anonymous reference to a method on an object that may not exist, and takes zero, one, or more parameters and turn calls Facade.Entity.GetName with that parameter. In your case, it doesn't pass any parameter to the method delegate, but as long as you don't execute the anonymous method, it only represents a signature for a method call on a class.

If you look at the definition of Anonymous methods, "they are methods without a name. We can write small method block without any name and assign its address to the delegate object, then this delegate object is used to call it."

Saeid
  • 1,573
  • 3
  • 19
  • 37