1

I have a service that needs to make a callback. Basically, it is an event that is expected to be handled in exactly one place, and that is too important to be optional.

The obvious approach seems to be to inject an Action. In the context of dependency injection, is it considered good (or acceptable) practice to do so?

I'm also eager to hear why (not), or what alternatives you would consider.

One particular problem that comes to mind is in the following scenario:

  • Parent's constructor takes an IChild.
  • Child's constructor takes an Action, the callback.
  • Parent has the method that is to receive the callback.
  • To instantiate the Child, we need the Parent's method, and thus the Parent instance. But to create that, we first need the Child instance. Problem.

One solution I can think of is to inject an IChildFactory instead. Parent's constructor can then use that factory to create the Child instance. At this point, Parent exists, and thus it can pass its callback method to the factory.

This solution seems to get the job done, but I'm curious about alternatives.

9
  • 1
    Once you're injecting a dependency as an abstraction (whether an object or function/method pointer), IMO it is an acceptable approach and you're able to get the benefits of DI. However, I would create an Interface and inject some implementation of it into your service. I think this would break even more any coupling between service and its client. Some frameworks can select implementations based on configuration file, and if some signature changes, you can easily find the right place to modify them. Commented Jan 8, 2018 at 15:54
  • @JoshCaswell I thought I'd added the C# tag! Now I have.
    – Timo
    Commented Jan 9, 2018 at 9:22
  • @EmersonCardoso I do believe that the same problem remains in my example: Parent needs Child, but Child needs Parent (which implements the interface you mention). How would you tackle this?
    – Timo
    Commented Jan 9, 2018 at 9:24
  • @Timo, you're right, the circular dependencies still exist regardless of the approach. About that, I'm confused about the following: (i) Parent has the method that is to receive the callback., and (ii) To instantiate the Child, we need the Parent's method. If the callback is implemented in the child, what does it mean that the Parent "receives the method" ? I don't understand that part.. Commented Jan 9, 2018 at 10:09
  • @EmersonCardoso Let's say the Child wants to call some log method, and Parent wants to dictate how logging is done. This would require us to pass Parent's Log() method (or Parent itself) to Child's constructor, thus creating the circular dependency.
    – Timo
    Commented Jan 9, 2018 at 14:40

3 Answers 3

1

Yes, injecting a callback is absolutely acceptable. I would usually prefer to do it this way.

An alternative, which you've already pointed out, is to define an event on the service object and use a factory to create it - making sure that it's the only way to create it and then have the factory handle the event, or delegate it further (which brings us back to square one).*

The specific parent/child example may have other solutions, depending on your context. For example, you could create the parent, and pass it to the child constructor, which could insert itself as the parent's child.

* Clarification: My suggestion was to either inject a callback or use a factory that registers an event handler in the regular way. In this case, to enforce your requirement that the event must be handled, only the factory should create service objects. Then no-one can create a service object without registering an event handler.

You may want to use a factory even with the injected callback for other reasons (e.g. to make sure the correct callback is provided). Also you may have multiple such factories. Those are separate considerations.

4
  • Thanks! I would not have the child insert itself on the parent, as I prefer to stick with readonly properties.
    – Timo
    Commented Jan 8, 2018 at 12:58
  • What is the reason for making sure the factory is the only way to instantiate the service?
    – Timo
    Commented Jan 8, 2018 at 12:59
  • 1
    @Timo I can't answer for doubleYou, but I suppose it would not be important if there is one or several factories. The point being that you use dependency injection in a factory separate from the service, as the service should not also create the callback (it should be decoupled from the action taken afterwards).
    – Neil
    Commented Jan 8, 2018 at 13:12
  • See my edits, I hope they answer your question.
    – doubleYou
    Commented Jan 8, 2018 at 13:38
1

As per EmersonCardoso's comment, we might inject the Parent into the Child via property injection:

  • Child is injected into Parent, by constructor injection.
  • In Parent's constructor, it sets itself on one of Child's properties.

This helps deal with the cyclic nature of the dependencies. The solution is much like having a public event property, except for only a single handler.

Disadvantages might be:

  • The dependency has become (compile-time) optional.
  • Child has become stateful. Stateless services are a good practice anyway, and even more so in DDD, for example.
  • Child has become externally mutable. Even if statefulness is deemed acceptable, external mutability is something I would avoid.
0

Why did I not think of this before?

We can simply inject into Parent's constructor a Func<Parent, Child>, a function that takes the Parent as a parameter and returns the [new] Child. (Alternatively, the function could accept the callback method instead of the entire parent - the idea is the same.)

In Parent's constructor, Parent exists, so it can pass [part of] itself to the function that creates Child. Then, it can assign the created Child to its read-only property.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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