Accessing a method which was assigned to a passed in Func<> parameter

102 views Asked by At

Looks like I've missed something obvious but really can't figure out why I can't use/access a method which was assigned to a passed in Func<> parameter, I want to access/call it in the external calling context.

Below the simple example: (real results data type is not string but some complex type)

class Caller
{
    private Func<string> factory;

    public Caller()
    {
        this.factory = null;
        var adapter = new Adapter(this.factory);
        adapter.Adapt(...);

        // then I would share this factory via IoC
        dependencyContainer.RegisterUsingFactoryMethod<string>(this.factory);

        // !!! but here is factory still not assigned
        string results = this.factory();
    }
}

class Adapter
{
    private string results;

    public Adapter(Func<string> factory)
    {
        factory = () => this.results;
    }

    public IOut Adapt(IIn input)
    {
       this.results = someCalculatedData;
    }
}
4

There are 4 answers

7
Jon Skeet On BEST ANSWER

This has nothing to do with delegates, and everything to do with the fact that all arguments are passed by value by default. You'd see the same behaviour if factory were an int, or a string, or a StringBuilder.

If you want your Service constructor to modify the value of the argument, you need a ref or out parameter. However, I would personally suggest that you change it to use a method which returns a Func<string>:

public class Caller
{
    private readonly Func<string> factory;

    public Caller()
    {
        this.factory = Service.GetFunc();
    }
}

public class Service
{
    public static Func<string> GetFunc()
    {
        return () => Guid.NewGuid().ToString();
    }
}

(There are various other options here, such as a method group conversion, or a property. It's hard to know what to recommend when we don't have much context.)

Obviously you'd want to rename the method. But calling a constructor solely to modify an out parameter would be a very odd bit of code.

For more on parameter passing, see my article on the topic.

2
GreyCloud On

You never assign to the factory in the Callee,

Since you are not passing the factory to the service by reference the updates to the variable will not be seen by the caller.

I am uncertain whether or not you can pass parameters to a contructor by reference or not...

1
Samuel Slade On

To call a Func<T...> reference, you just need to call as though it were a normal method. i.e.:

class Service
{
    public Service(Func<string> factory)
    {
        string result = factory();
    }
}

Note that Func<string> will return a string type.

EDIT

From your comment I have realised what you were actually asking in the OP. To be able to assign a value to any parameter, you need to mark it with out or ref. So your declaration would look like:

public Service(out Func<string> factory)

or

public Service(ref Func<string> factory)
2
Dr. Andrew Burnett-Thompson On

Can you try pasing the func with an out parameter to the constructor of Service?

e.g.

class Caller
{
    private Func<string> factory;

    public Caller()
    {
        this.factory = null;
        var service = new Service(out this.factory);

        // !!! but here is factory still not assigned
        string results = this.factory();
    }
}

class Service
{
    public Service(out Func<string> factory)
    {
        factory = () => Guid.NewGuid().ToString();
    }
}

This will ensure the compiler knows to initialize the reference to factory parameter in the Service constructor. Otherwise you are just passing a reference to the null Func to the constructor by value then locally assigning the function to that variable on the stack inside the ctor.

Please see this related question for clarity on ref and out parameters.

Best regards,