Can dynamic classes use dependency injection in EK9?

← Dependency Injection · Ref: Q233

Dynamic classes (anonymous trait implementations) can have injection fields. The compiler follows the call graph from programs through functions to find all dynamic class instantiations and validates their injection fields.

DYNAMIC CLASS WITH INJECTION

A dynamic class created with '() with trait of' can declare '!' fields:

  handler <- () with trait of Processable
    logger as Logger!
    override process()
      <- rtn as String: logger.log("processing")

The Logger injection is resolved from the application context.

CALL GRAPH ANALYSIS

The compiler traces the execution path from the program through any functions that create dynamic classes. If a function creates a dynamic class with injection fields, those fields must be satisfiable from the program's application.

WHY THIS IS UNIQUE

Most DI frameworks only support injection on named, registered classes. EK9 extends injection to anonymous inline implementations, which is powerful for callbacks, handlers, and one-off implementations that still need access to registered services.

COMPILE-TIME SAFETY

As with all EK9 DI, dynamic class injection is validated at compile time. If the required component is not registered, the compiler reports the error.

See Q227 for compile-time validation. See Q111 for component basics. See Q115 for dynamic class basics. See Q234 for component lifecycle.

Example

defines module qa.di.dynamic

  defines trait

    Processor
      process() as abstract
        -> input as String
        <- output as String?

      override operator ? as pure
        <- rtn as Boolean: true

  defines component

    Logger as abstract
      log() as abstract
        -> message as String
        <- result as String?

      default operator ?

    ConsoleLogger is Logger
      override log()
        -> message as String
        <- result as String: "LOG: " + message

      default operator ?

  defines function

    createProcessor()
      <- handler as Processor?

      // === DYNAMIC CLASS WITH INJECTION ===
      handler: () with trait of Processor
        logger as Logger!

        override process()
          -> input as String
          <- output <- String()
          output: logger.log(input)

        default operator ?

  defines application

    DynamicApp
      register ConsoleLogger() as Logger

  defines program

    DynamicInjectionDemo() with application of DynamicApp
      stdout <- Stdout()

      // === FUNCTION CREATES DYNAMIC CLASS WITH INJECTION ===

      handler <- createProcessor()
      result <- handler.process("test message")
      stdout.println(result)

      stdout.println("Dynamic class injection validated at compile time")

Common mistakes

E08150 — Even in dynamic classes, injection fields must reference abstract component types. The concrete type is determined by the application registration, not the injection field declaration. See ek9 -h E08150 for details.

Incorrect:

logger as ConsoleLogger!

Correct:

logger as Logger!

E07160 — Removing the only registration from the application leaves it empty, requiring at least one implementation to be provided. See ek9 -h E07160 for details.

Correct:

register ConsoleLogger() as Logger
Other ways to ask this
  • Do anonymous trait implementations support injection in EK9?
  • How does DI work with inline classes in EK9?
  • Can I inject components into dynamic classes?

Coming from another language?

Java: anonymous inner classes cannot use @Autowired, must receive dependencies via constructor or enclosing scope. Guice: no injection into anonymous classes. .NET: no injection into anonymous types. Python: no built-in DI for anonymous objects. Go: no anonymous classes. Rust: no anonymous classes with DI. EK9: dynamic classes with '() with trait of' support '!' injection fields, compiler traces call graph to validate.

Keywords: trait, capture, anonymous, call, dynamic, injection, inject, class, graph, inline, handler, closure, callback