Self-invoking anonymous functions
Asked Answered
G

3

23

In JavaScript, it's not uncommon to see self-invoking functions:

var i = (function(x) {
    return x;
})(42);

// i == 42

While I'm certainly not comparing the languages, I figured such a construct would be translatable to C#, provided a language version with support:

var i = (delegate(int x) {
    return x;
})(42);

Or:

var i = ((x) => {
    return x;
})(42);

Or even:

var i = (x => x)(42);

However, every version is in error:

Method name expected

Are self-invoking anonymous methods unsupported (due either to explicit forbiddance, or the impossibility of type inference), or is there an error in my attempts?

I'm venturing a guess that because there was no method declaration (Func<T,T>) against which the types could be inferred, it can't sort out the implied types, figures I meant to call a declared method by name, and really goofed up the syntax.


Errata

Before this question is flooded with:

var i = new Func<int, int>(x => x)(42);

I should say that I was hoping to leverage type inference, but I suppose that may not be possible due to being overly-implicit.

So, clarifying the question a bit; we know we can var i = new Func<int, int>(x => x)(42); but without creating an instance of, or casting to Func, is this possible?

Use-Case

For those curious (or concerned) the use case was something like this:

var o = new {
    PropertyA = () => {
        // value computation
    }(),
    PropertyB = () => {
        // value computation
    }(),
};
Gynecoid answered 7/3, 2013 at 18:29 Comment(15)
Consider why you need this construct in javascript, and apply that concept to c#.Sleuth
@Sleuth While I appreciate the "teach a man to fish" approach to your comment, a little elaboration would be appreciated ;)Gynecoid
What exactly does a self invoked function accomplish in javascript?Sleuth
#1209203Kirksey
AFAIK the only true and reasonable case you may think to use self-invoking methods is this. Any use-case in C#?Reckless
@Sleuth it allows you to create a private closure that contains private variables only accessible by the object you return from the function. Code consuming the returned function cannot get to those private variables. See this questionMilagrosmilam
@Sleuth Sorry (between many things), what Brandon said; additionally, for the sake of brevity which is what I was going for.Gynecoid
@Milagrosmilam The point, I guess, is if it's a good thing to do or not. If you write a long anonymous function maybe your code will be more clear if you write a true named function.Reckless
@Bracketworks about your "errata": C# is typed...Reckless
@Adriano Of course; is the typing simply too implicit to infer from? I certainly agree with you point on creating a named method if the procedure is long enough to warrant one; I was trying to use very short methods so it should have been applicable.Gynecoid
@Bracketworks So you need an object that can contain private variable values, that can expose properties and other functions? I think there is a word for this type of construct in c#...Sleuth
@Sleuth Funny guy. No, I was trying to leverage the brevity of anonymous methods and fluent interfaces for a specific use-case. I don't need to protect or expose any data; I was after a brief way to inline statements where they would otherwise be syntactically erroneous.Gynecoid
@Bracketworks Why play syntax tricks when the language gives you the feature for free? Consider code maintenance. What do you think Bracketworks(+5 years) is going to think about your tricks when trying to track down some subtle bug?Sleuth
@Sleuth I'm completely for readability, and human parse-ability. I appreciate your concern. I highly doubt this use-case would have been any more or less difficult to understand than the alternatives; merely shorter.Gynecoid
@Bracketworks I didn't catch the point you're playing with the syntax, in this case what's better or readable is pretty irrelevant. I posted an answer about it.Reckless
R
11

I suppose someone from the C# team can give a much better answer but I try my shot. Question here is not if it's a best practice or not but if it's possible and, if not, why.

Let's start with what you would write:

var i = (x => x)(42);

Pretty straightforward, you pass an integer (42) to a lambda, it plays with it and it returns the same value. Its parameter is an integer (inferred from its usage), it returns an integer (inferred from the expression) and i is another integer (inferred from return value).

Unfortunately this is broken in the very first step. Let me cite big Jon:

Compiler tries to work out the types of the parameters for lambda expressions based on the context in which it's used.

What does it mean? Actually that the compiler need some information but it can use only:

  • An explicit cast, constructor or declaration like in (Func<int, int>)(x => x)) or new Func<int, int>(x => x). Required types here are given or inferred using the required final type.
  • An assignment (again because the final type can be inferred) like in: Func<int, int> f = (x => x);

In your case the compiler have to infer function type from its parameters and it's not possible because parameters are validated against the expression and not vice-versa.

Why this is not possible in C#? Because (x => x) is just a delegate then it can be any delegate that accept an integer parameter (if we suppose the compiler can infer lhs from rhs and validate rhs according to lhs) and returns another integer. Actually conversion between delegates are not allowed in C# so the expression is invalid (even if this special delegate won't be assigned to any variable and it won't be used in the future). It could be Func<int, int> or Expression<Func<int, int>> or any other delegate (for bool parameter it may even bee Predicate<bool>).

Of course this is a C# design decision, the same expression - for example - in VB.NET is perfectly valid:

 Dim i = (Function(x) x)(42)

Different language, different rules to obey, C# goal is to avoid this kind of ambiguity.

Reckless answered 8/3, 2013 at 8:48 Comment(0)
M
20
var x = ((Func<int, int>)(y => y * 2))(10);

The problem is that when the compiler sees y => y * 2 it classifies it as an Expression instead of a Func by default, unless there is some contextual information to let it know it should be a Func. By casting it to Func we give it the context it needs.

Milagrosmilam answered 7/3, 2013 at 18:33 Comment(4)
var i = new Func<int, int>(x => x)(42); syntax is also valid.Crayon
@Caramiriel: ah yeah I've never considered using the constructor syntax :)Milagrosmilam
If that is indeed the case; that the compiler sees x => x and interprets it as an expression, then why wouldn't the delegate alternative work? (or is that too assumed to be an expression?)Gynecoid
@Bracketworks: I believe it is because an anonymous method declared with delegate is not fully typed until it is assigned to something. Again it needs context. This works: ((Func<int, int>)delegate(int y) { return y * 2; })(10);. See MSDNMilagrosmilam
R
11

I suppose someone from the C# team can give a much better answer but I try my shot. Question here is not if it's a best practice or not but if it's possible and, if not, why.

Let's start with what you would write:

var i = (x => x)(42);

Pretty straightforward, you pass an integer (42) to a lambda, it plays with it and it returns the same value. Its parameter is an integer (inferred from its usage), it returns an integer (inferred from the expression) and i is another integer (inferred from return value).

Unfortunately this is broken in the very first step. Let me cite big Jon:

Compiler tries to work out the types of the parameters for lambda expressions based on the context in which it's used.

What does it mean? Actually that the compiler need some information but it can use only:

  • An explicit cast, constructor or declaration like in (Func<int, int>)(x => x)) or new Func<int, int>(x => x). Required types here are given or inferred using the required final type.
  • An assignment (again because the final type can be inferred) like in: Func<int, int> f = (x => x);

In your case the compiler have to infer function type from its parameters and it's not possible because parameters are validated against the expression and not vice-versa.

Why this is not possible in C#? Because (x => x) is just a delegate then it can be any delegate that accept an integer parameter (if we suppose the compiler can infer lhs from rhs and validate rhs according to lhs) and returns another integer. Actually conversion between delegates are not allowed in C# so the expression is invalid (even if this special delegate won't be assigned to any variable and it won't be used in the future). It could be Func<int, int> or Expression<Func<int, int>> or any other delegate (for bool parameter it may even bee Predicate<bool>).

Of course this is a C# design decision, the same expression - for example - in VB.NET is perfectly valid:

 Dim i = (Function(x) x)(42)

Different language, different rules to obey, C# goal is to avoid this kind of ambiguity.

Reckless answered 8/3, 2013 at 8:48 Comment(0)
A
1

I worked around the language limitation using overloaded static method that accepts an anonymous function and executes it:

namespace Utils {
    public static class InvokeInlineHelper {
        public static T Invoke<T>(Func<T> f) {
            return f();
        }

        public static T Invoke<A1, T>(Func<A1, T> f, A1 a1) {
            return f(a1);
        }

        //...overloads with more than 1 argument
    }
}

The usage is quite simple and it maintains type-safety. This is my Program.cs file:

global using static Utils.InvokeInlineHelper;

const string prefix = "Value";

var o = new
{
    PropertyA = Invoke(() => {
        return prefix + "A";
    }),
    PropertyB = Invoke((input) => {
        return prefix + input;
    }, "B"),
};

Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(o));
//Outputs: {"PropertyA":"ValueA","PropertyB":"ValueB"}

Note the global static using which improves the syntax. Closures can be still leveraged, of course, as demonstrated with prefix variable.

Arquebus answered 1/2, 2023 at 7:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.