3

Consider this example of method injection:

static void SaveEmail(Func<string> getEmailFunction)
{
    dbcontext.SaveEmail(getEmailFunction());
}

static string GetEmail()
{
    var frmUser = GetUserForm();
    return frmUser.GetEmail();
}

static void ExampleCall()
{
    SaveEmail(GetEmail);
}

In this example, we are injecting a Func<string> that returns the email address of the user. The SetEmail method uses this as a sort of callback to obtain the user's address. This way we avoid SetEmail having a hardcoded dependency on the code that obtains the user's form and reads its fields.

Is it possible to do this without the static version of GetEmail? Something like this:

static void SaveEmail(Func<string> getEmailFunction)
{
    dbcontext.SaveEmail(getEmailFunction());
}

static void ExampleCall()
{
    var frmUser = GetUserForm();
    SaveEmail(frmUser.GetEmail);
}
  • 6
    Yes, that's totally possible. And it looks like the example in your second code block is the right syntax. Have you experienced a problem with it? – Erik Mar 14 '17 at 21:33
  • Same sort of syntax if I want my Func<T> to point at a property? Or does it have to be a method? – John Wu Mar 14 '17 at 21:37
  • 5
    If you want it to point to a property, you'll need a lambda: SaveEmail(() => frmUser.EmailProperty); – Erik Mar 14 '17 at 21:38
  • So you want to take well named and defined method, obfuscate it, move it out of the class it belongs in, double the amount of written code and spend more time testing, all for the falacious illusion that the dependency was removed? No, it was only moved. – radarbob Sep 17 '17 at 16:53
  • I am asking if something is possible, not fit for a particular purpose. The code snip is an example chosen for its ease of explanation. Certainly injected methods are useful in other contexts, e.g. for deferred execution. – John Wu Sep 17 '17 at 20:30
1

Sure you can but why? This isn't a question of why you would ever do that but why you should do that as opposed to injecting a class that does it for you via interface (dependency injection). You could pass in the class to the method call or to the constructor of the class that has the save email (IOC containers prefer the second approach).

So in the case that you presented, this will work just fine if you have short methods that do not go crazy, because then you may as well have a class do that for you. In fact, there is nothing against having a class that only has like 10 lines of code in it. The new "microservice" architecture ideas could have 10 lines of code in an entire program.

So you could have an interface that looks something like this:

public interface IEmailProvider
{
    string GetEmail();
}

And a class that does something easy:

public class SettingsEmailProvider : IEmailProvider
{
    public string GetEmail()
    {
        // sudo code read from app settings
        return ReadFromSettingFile("useremail");
    }
}

But now you need to test that code so you create a moq class

public class MoqEmailProvider : IEmailProvider
{
    public string GetEmail()
    {
        return "foo@bar.com";
    }
}

Ok so far we could have just done this in a function call passed in, but lets say you need something more complicated in the future, like go into a Facebook API to get the user's email address from there.

public class FacebookEmailProvider : IEmailProvider
{
    private readonly IFacebookCredentialProvider _creds;
    private readonly IFacebookClient _client;
    private readonly IEmailProvider _fallbackProvider;

    public ILog Logger {get;set;}

    public FacebookEmailProvider(IFacebookCredentialProvider creds, IFacebookClient client, IEmailProvider fallbackProvider)
    {
        _creds = creds;
        _client = client;
        _fallbackProvider = fallbackProvider;
    }

    public string GetEmail()
    {
        //Try to get the email from facebook
        string email;
        try
        {
            var credentials = _creds.GetCredentials();
            var task = Task.Factory.StartNew(()=>{
                _client.Login(credentials);
                email = _client.GetUserEmail();
                }
            task.wait();
            if(string.IsNullOrEmpty(email))
                email = _fallbackProvider.GetEmail();
        }
        catch(Exception ex)
        {
            Logger.Error(ex);
            email = _fallbackProvider.GetEmail();
        }
    }
}

So now we have gone a bit more complicated, and then your calling class will need to provide all of this if it was just a Func. Instead it would be easier to deal with if it was in it's own class (my opinion).

This is of course creating more classes for you to maintain but trying to make it more testable.

In short, yes you can but you should choose an approach that matches your situation and needs for your project. I like the dependency injection approach because it is testable, readable and easy to expand into something more complex without changing the primary code, but always go with what you feel comfortable with as well as what is the best for your project / team.

  • 2
    One would want to inject the function precisely to avoid all of this extra complexity. Closures are functionally equivalent to objects, especially in this case. You could absolutely have a "get the email from facebook" function, and it could absolutely be "dependency injected" in basically the same style. (creds, client) => () => { get facebook email} or some such. It's much, much more awkward in C# given the awkward function type syntax and rules, though, unfortunately. In F#, the approach in the question might be preferred to the approach in this answer, but not in C#. – Phoshi Jun 19 '17 at 14:40
  • Func<T> is a type in same way as other types In C#. it's perfectly "injectable" too. Especially when you works with static or pure functions. Injecting instance method with DI can be tricky, because you need to handle life-cycle of instance manually. – Fabio Oct 18 '17 at 5:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.