6

On the way home I had an idea: create Func/Action extensions which would allow some nice syntactic sugar in c#.

Theoretical example... create an extension for various permutations of Func/Action which allow you to time the method's execution.

As I arrived home and tried an example, I found this is not possibly. I believe it is a shortcoming/inconsistency in c#. Delegates and methods are one in the same (in theory).

public static class Extensions
{
    public static void Time(this Action action)
    {
        // Logic to time the action
        action();
    }
}

public class Example
{
    public void Main()
    {
        Action action = RunApp;
        Action actionLambda = () => { };
        Action actionDelegate = delegate () { };

        Extensions.Time(RunApp); // Works
        Extensions.Time(() => { }); // Works
        Extensions.Time(delegate() { }); // Works
        Extensions.Time(action); // Works
        Extensions.Time(actionLambda); // Works
        Extensions.Time(actionDelegate); // Works

        action.Time(); // Works
        actionLambda.Time(); // Works
        actionDelegate.Time(); // Works

        ((Action) RunApp).Time(); // Works
        ((Action) delegate () { }).Time(); // Works
        ((Action) (() => { })).Time(); // Works

        // These should all be the same! 

        RunApp.Time(); // No good: "Example.RunApp() is a method which is not valid in the given context"
        () => { }.Time(); // No good: Operator '.' cannot be applied  to operand of type 'lambda expression'"
        (() => { }).Time(); // No good: Operator '.' cannot be applied  to operand of type 'lambda expression'"
        delegate() { }.Time(); // No good: "Operator '.' cannot be applied operand of the type 'anonymous method'"
    }

    public void RunApp()
    {
        // Stuff...
    }
}

I understand Func/Action are newer additions to c# compared to delegates and method groups, but why can they all not act the same?

smdrager
  • 7,327
  • 6
  • 39
  • 49
  • 1
    Action encapsulates a method, so, like your code shows, you have to explicitly say to encapsulate something as an Action – Keith Nicholas Sep 14 '16 at 22:52
  • 1
    your last line needs to be `((Action)(() => { })).Time();` – Keith Nicholas Sep 14 '16 at 22:53
  • Added it as another "works" and reset the original. – smdrager Sep 14 '16 at 23:06
  • What answer would satisfy your "why"? A spec reference? Some email from someone on the language design team talking about this? It's unclear what you're looking for in an answer, here. Why _should_ methods work syntactically like delegates? – Will Ray Sep 14 '16 at 23:20
  • Glad you asked. As I finished the question I realized it is kind of rhetorical. But as with all things in programming where things seem like they "should be" there are often clear answers from below (framework) that perfectly answer the why. We can all wish for things to work a certain way, but I'll settle for "Here is why in the c# spec and you should put in a feature request." One of the reasons I love this site is that generally when a question is submitted such as this, it is answered by those who are equipped to answer. – smdrager Sep 14 '16 at 23:45
  • Thank you so much @Eric Lippert for contributing to the answer. The related question and question provides a good deal of context. It seems the answer for now is that there is enough time. I wish the answer was given more than 8 months ago because that might mean there could be another answer for the c# arch community. From the related answer these are contested: For lambdas, group methods, and nulls, if the type can be inferred (as it is elsewhere) it should be. If not, throw an exception as other ambiguous references do. Linq-essential or not, this is not predictable behavior. – smdrager Sep 15 '16 at 01:47

1 Answers1

0

Action just nicely encapsulates delegates in a simple way without having to explicitly declare your delegates. In C# delegate is the language mechanism for capturing a method as a type. A type is what you need to create an extension method on.... so, simplifying back to delegates...

 public static class Extensions
    {
        public delegate void Del();
        public static void Time(this Del action)
        {
            // Logic to time the action
            action();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            
            ((Extensions.Del)(() => { })).Time();
        }
    }

the language won't automatically cast Methods to Del, you need to explicitly do so. Methods themselves aren't a type, delegates are the mechanism to capture them. Fundamentally, in c#, delegate is not the same as method. It's C#s solution to having typesafe pointers to methods.

Another thing to keep in mind is you can do :-

 public static class Extensions
    {
        public delegate void Del();
        public delegate void Del2();
        public static void Time(this Del action)
        {
            // Logic to time the action
            action();
        }
        public static void Time(this Del2 action)
        {
           // Logic to time the action
           action();
        }


        public static void Time(this Action action)
        {
            // Logic to time the action
            action();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {            

            ((Extensions.Del)(() => { })).Time();
            ((Extensions.Del2)(() => { })).Time();
            ((Action)(() => { })).Time();

            (() => { })).Time();   // no way to know what type this should be coerced to

        }        
    }

Now your methods could be captured as many different types.

Keith Nicholas
  • 43,549
  • 15
  • 93
  • 156
  • I appreciate your time to think through the question but your answer does not answer it. Why would you make a Del and Del2 in the extensions class? – smdrager Sep 14 '16 at 23:54
  • the point is, they are just types that can capture methods.... you can make as many of them as you like. Extension methods work on types. There's no direct path from Method -> Type as a Method can be captured by many different types. – Keith Nicholas Sep 15 '16 at 01:14
  • In your example, you are somehow wanting the language to coerce a method into your exact type, but there are potentially many types a method could be – Keith Nicholas Sep 15 '16 at 01:15
  • There is a one-to-one mapping within the syntax laid out. There cannot be more than one method within a class and namespace that describes Action for a given name. – smdrager Sep 15 '16 at 01:31
  • but any method could match any compatible delegate defined in the framework right? including Action. So why would it cast it to an Action and then call your extension Method? – Keith Nicholas Sep 15 '16 at 01:46
  • Please provide code for more than one method which matches a signature for an action within a single namespace and class. Use code. – smdrager Sep 15 '16 at 01:50
  • ^--- the code above. Action is just a wrapper around delegate. I show multiple delegates ( and Action ) that map to the same kind of method ( void void ) meaning there is no simple way to work out which Time extension method should be invoked – Keith Nicholas Sep 15 '16 at 02:03
  • The additional properties you provided are not conflicting signatures. I'm pretty confused. The whole point of the extension I provided to to be generic. I don't think we are communicating effectively. Would it be worthwhile for me to share my email with you so we can share more directly? – smdrager Sep 15 '16 at 02:07