Get "Object" return type from Func<MyClass, object>
Asked Answered
J

3

6

Let's suppose I have defined Func as follows:

Func<MyClass, object> f = o => o.StringProperty;

or

Func<MyClass, object> f = o => o.Property.SomeMethod();

Is there way to get the actual return type without specifically calling it?

Junia answered 24/7, 2013 at 14:29 Comment(11)
"Get" the return type how exactly? A hypothetical example?Gladdie
@Gladdie I think inferring the return type based on the fact the compiler knows this (string in the first example, return of SomeMethod in the second), however this example would collapse at run-time as the variable f could be re-assigned. Hypothetically, if f could not be re-assigned you could statically determine the return type by parsing the Func definition. Sounds like a candidate for Roslyn ;-)Felicitous
Depending on your usage, perhaps you can leverage the Expression API to check the return type: #672468 (this isn't an exact implementation of what you're trying to do, just a related example)Topazolite
What would the "actual" return type be if you have something like Func<MyClass, object> f1 = o => o.StringProperty; Func<MyClass, object> f2 = o => f1(o);? Or even Func<MyClass, object> f = o => { throw new NotImplementedException(); };? :)Goff
@hvd it's a good question in the case of throwing exceptionJunia
@EugeneD.Gubenkov: Are you retrieving these f delegates at runtime and are specifically typed as Func<MyClass, object>? Or would a slight change of syntax be ok, something like: MemberInspector<MyClass>.GetReturnType(o => o.StringProperty)?Topazolite
@ChrisSinclair, yes I retrieving these delegates at runtime and they are typed as Func<MyClass, object>Junia
Then I think you may be out of luck. Perhaps you can change your design such that these delegates are defined at compile-time, or wrapped as an object that also stores the return type information where/when the delegates are defined. EDIT: Do you have the ability to change how callers define these delegates? Could you force the API they use to be something like MyClassDelegate f = MyClassDelegate.Create(o => o.StringProperty) rather than the code you posted?Topazolite
(cont.) And work with this custom class rather than a Func<MyClass, object> directly? The custom class can have an Invoke method that can invoke the delegate as well.Topazolite
@ChrisSinclair, I don't completely get it. What did you mean when you say "delegates wrapped as an object"?Junia
@EugeneD.Gubenkov: I posted an answer with an example and usage.Topazolite
T
5

You can get the return type like this:

f.Method.ReturnType

But this will return you the type object. If you want to get Method or String or something that derives from object, you won't be able to have the information unless you call the method.

Actually you could, but this would mean that you'd have to dissassemble the method core and then analyze it to see what the method can return. But even then, there might be many different return types.

So the answer is: if you want to know that it returns an object, then yes you can, otherwise it's not worth the trouble, and it's better to find another way of doing what you need.

Taraxacum answered 24/7, 2013 at 14:36 Comment(0)
T
2

Since you are retrieving these Func<MyClass, object> delegates at runtime from other sources, the type information is essentially lost.

Instead, where these functions are defined, you can have the callers essentially encode that type information in a wrapped delegate by taking advantage of the LINQ Expression API (EDIT: Silly me, far more simpler at this point; we already have the generic compile time information):

public class MyClassDelegate
{
    private readonly Func<MyClass, object> Function;

    public Type ReturnType { get; private set; }

    private MyClassDelegate(Func<MyClass, object> function, Type returnType)
    {
        this.Function = function;
        this.ReturnType = returnType;
    }

    public object Invoke(MyClass context)
    {
        return Function(context);
    }

    public static MyClassDelegate Create<TReturnType>(Func<MyClass, TReturnType> function)
    {
        Func<MyClass, object> nonTypedFunction = o => function(o);
        return new MyClassDelegate(nonTypedFunction, typeof(TReturnType));
    }
}

(A derived generic MyClassDelegate<TReturnType> : MyClassDelegate class could be made as well to get around some of the sillyness in the Create method, or avoid value-type boxing, or to have the return type information available at compile time or even by reflecting on whatever MyClassDelegate<TReturnType> is.)

Callers defining the delegates instead of working directly with a Func<MyClass, object> would instead work with this class and define their delegates as:

MyClassDelegate f1 = MyClassDelegate.Create(o => o.StringProperty);
MyClassDelegate f2 = MyClassDelegate.Create(o => o.Property.SomeMethod());

Your API would require a MyClassDelegate, with which you can easily access their types:

Console.WriteLine(f1.ReturnType.FullName); //string
Console.WriteLine(f2.ReturnType.FullName); //whatever `SomeMethod()` is declared to return

Finally, you can invoke the delegates or even create Func<MyClass, object> delegates still:

f1.Invoke(myClassInstance);
Func<MyClass, object> f3 = f1.Invoke;
Topazolite answered 24/7, 2013 at 15:18 Comment(4)
@EugeneD.Gubenkov: I've edited my answer; the LINQ expression API is unnecessary at this point. The key takeaway is to have callers encode this information when the delegate is created and leverage a wrapped delegate to pass the information throughout your API.Topazolite
Thank you for your detailed answer. When we want to do such things properly we should use Expressions anyway, I get it.Junia
The Expression usage really only helps if you want to avoid the generic inference. That is, you could have Create(Expression<Func<MyClass, object>> function) and still determine the type used. But you may as well take advantage of generic type inference I used in my update.Topazolite
Yes, I just wanted to find not compile-time solution, without generic types.Junia
G
1

You can do something close to this by using a generic method to make the compiler infer the type arguments:

static Func<T1, R> Infer<T1, R>(Func<T1, R> f) { return f; }

And then:

var func = Infer((string s) => s.Length);

This will encode the return type into the type of func at compilation time.

Of course for a more generally applicable solution you would need a bunch of overloads of Infer to cover Action and Func with one, two, three, etc arguments.

If you then want to get the return type at runtime, for any kind of Func it's as simple as func.Method.ReturnType as ppetrov has already pointed out.

Gladdie answered 24/7, 2013 at 14:36 Comment(2)
@EugeneD.Gubenkov: Of course, if you already have Func<string, object> f = s => s.Length and you do Infer(f) you will get back a Func<string, object> -- there is no way to recover type information that has been "lost".Gladdie
@EugeneD.Gubenkov: If you want to do more then you have to work with Expression<Func<...>> and reflection instead.Gladdie

© 2022 - 2024 — McMap. All rights reserved.