Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

make method in C# that takes function without type parameters

Tags:

c#

.net

My eventual aim is to get a function that can take any other function - eg something like

      void RegisterHandler( Delegate function);

however - I want to be able to call it without doing any casting, or without type parameters - obviously Delegate doesn't work. If I declare:

      void RegisterHandler<T>( Func<T> function );

then it works fine - this works:

      string Whatever() {return "hi";}
      ...
      RegisterHandler( whatever );

however - as soon as I throw in parameters, it doesn't work

      void RegisterHandler<TParam1,TReturn>( Func<TParam1, TReturn> function );
      string Something( int x ) {return x.ToString();}
      ...
      RegisterHandler( Something );               //  doesn't compile - wants types specified
      RegisterHandler<int,string>( Something );   // works, but is what I'm trying to avoid

I'd even be ok if "object" worked - this is just part of a fluent interface, and I just want to be able to call it. I read somewhere that the number of parameters has to be >= the number of template types, and I guess the return type is counted, so I can never fulfill that.

Bottom line - I can't find any c# signature that will accept any function that takes one parameter, and returns something. Is it possible?

like image 429
Darren Oakey Avatar asked Dec 05 '22 08:12

Darren Oakey


1 Answers

Rather than attacking your problem directly, which needs some clarification, let me address your concerns here:

I read somewhere that the number of parameters has to be >= the number of template types

Let's crisp that up a bit.

First off, those are generic type parameters, not "template types". This isn't C++. Generics are similar to templates but they are not templates, and the sooner you stop thinking of them as templates, the better.

It is NOT true that the number of formal parameters needs to be greater than or equal to the number of generic type parameters declared by the generic method. For example:

static void M<K, V>(Dictionary<K, V> d) { }
...
Dictionary<int, string> d = whatever;
M(d); // No problem!

The number of formal parameters is one, the number of generic type parameters is two, but we have no problem doing type inference here.

The real rule is quite a bit more complicated. Rather, the real problem that you're running into is:

The conversion of a method group to a delegate requires that the parameter types of the delegate be known before the conversion happens.

Let's suppose we have:

int F(string x) { ... }
void M<A, R>(Func<A, R> f) { ... }
M(F);

What happens? We must determine what F means when converted to Func<A, R> but we know neither A nor R. How do we determine the meaning? We do overload resolution. That is, we would pretend that there was a call:

A a = whatever;
F(a)

and ask "which method named F would work?"

But we never even get to that step because we don't know what A is yet. Type inference fails to make progress. Now, if by contrast you had:

int F(string x) { ... }
void M<A, R>(A a, Func<A, R> f) { ... }
M("abc", F);

Now type inference first says "I deduce from the use of "abc" for a that A is string." After that inference is made, now overload resolution would succeed. If we did

string a = whatever;
F(a);

then overload resolution would determine that F means int F(string).

Once we have determined that F means int F(string), now we can ask the question "what can we deduce about the conversion from int F(string) to Func<string, R>, and from that we deduce R must be int and we're done.

I know what you're going to ask next. I only have one overload called F, so why don't we just pick it automatically?

There are many problems with making exceptions like that. First, special cases tend to multiply, and soon we have an even crazier inference algorithm that no one understands and it cannot be changed without causing bugs. Second, it makes your code brittle; it means that inference depends on the number of accessible methods named F in scope. Suppose you add a new private method also called F; does inference suddenly change?

No, the rule is straightforward and understandable once you know it. Method groups are resolved exactly the same as though there was a call to the method. But we cannot simulate a call until after the argument types are inferred.

Believe me, I know as well as anyone how tricky the type inference algorithm can be in C#; it has a lot of these sorts of surprising cases. If you have a more crisp question about the design, implementation or specification of this algorithm feel free to open up a new question and leave me a comment on this answer and I will try to have a look.

like image 110
Eric Lippert Avatar answered Feb 19 '23 07:02

Eric Lippert