Visual Studio Code uses a comprehensive dependency injection (DI) system to manage services throughout the application. This system enables loose coupling, testability, and dynamic service replacement.
Key files:
src/vs/platform/instantiation/common/instantiation.ts - DI interfaces
src/vs/platform/instantiation/common/instantiationService.ts - DI implementation
src/vs/platform/instantiation/common/descriptors.ts - Service descriptors
Services are identified using unique service identifiers created with the createDecorator function:
Copy
Ask AI
// src/vs/platform/instantiation/common/instantiation.ts:109import { createDecorator } from 'vs/platform/instantiation/common/instantiation';// Define the service identifierexport const IMyService = createDecorator<IMyService>('myService');// Define the service interfaceexport interface IMyService { readonly _serviceBrand: undefined; // Brand for type safety doSomething(): void; getValue(): string;}
Why Service Identifiers?
Service identifiers serve multiple purposes:
Type Safety: TypeScript ensures you inject the correct service type
Runtime Resolution: The DI container looks up services by identifier
Decoration: The identifier acts as a parameter decorator for injection
Uniqueness: String IDs prevent naming collisions
Copy
Ask AI
// The identifier is both a type and a decoratorfunction (target: Function, key: string, index: number) { // Stores dependency metadata}
The _serviceBrand Property
Every service interface must include readonly _serviceBrand: undefined:
Important: Service dependencies are declared using the @IServiceName decorator syntax. The DI container will automatically inject these dependencies.
3
Register the Service
Copy
Ask AI
// In a registration file (e.g., workbench.common.main.ts)import { registerSingleton } from 'vs/platform/instantiation/common/extensions';import { IMyService, MyService } from './myService';registerSingleton(IMyService, MyService, InstantiationType.Delayed);
The IInstantiationService is the heart of the DI system:
Copy
Ask AI
// src/vs/platform/instantiation/common/instantiation.ts:52export interface IInstantiationService { readonly _serviceBrand: undefined; // Create an instance with dependencies injected createInstance<T>(ctor: new (...args: any[]) => T, ...args: any[]): T; // Invoke a function with service access invokeFunction<R>(fn: (accessor: ServicesAccessor, ...args: any[]) => R, ...args: any[]): R; // Create a child container with additional services createChild(services: ServiceCollection, store?: DisposableStore): IInstantiationService; // Dispose the instantiation service and all created services dispose(): void;}
// Create an instance with automatic dependency injectionconst myService = instantiationService.createInstance(MyService);// With additional constructor argumentsconst configured = instantiationService.createInstance( ConfigurableService, 'config-value', { option: true });
The instantiation service:
Analyzes constructor parameters
Resolves service dependencies from the container
Injects services after non-service parameters
Constructs and returns the instance
Copy
Ask AI
// Execute a function with access to servicesconst result = instantiationService.invokeFunction(accessor => { const logService = accessor.get(ILogService); const fileService = accessor.get(IFileService); logService.info('Executing function'); return fileService.exists(someUri);});
Use invokeFunction when:
You need services temporarily
You’re not creating a class instance
You want explicit service access
Copy
Ask AI
// Create a scoped container with additional servicesconst childServices = new ServiceCollection();childServices.set(IScopedService, new ScopedService());const childContainer = instantiationService.createChild(childServices);// Use child container - it inherits parent servicesconst instance = childContainer.createInstance(MyComponent);// Dispose when done - cleans up child services onlychildContainer.dispose();
Services are registered using the registerSingleton function:
Copy
Ask AI
import { registerSingleton, InstantiationType} from 'vs/platform/instantiation/common/extensions';// Eager instantiation - created immediately at startupregisterSingleton( IMyService, MyService, InstantiationType.Eager);// Delayed instantiation - created when first requestedregisterSingleton( IMyService, MyService, InstantiationType.Delayed);
// src/vs/platform/instantiation/common/instantiationService.ts:50dispose(): void { if (!this._isDisposed) { this._isDisposed = true; // Dispose all child containers dispose(this._children); this._children.clear(); // Dispose all services created by this container for (const candidate of this._servicesToMaybeDispose) { if (isDisposable(candidate)) { candidate.dispose(); } } this._servicesToMaybeDispose.clear(); }}
Services that implement IDisposable are automatically disposed when the instantiation service is disposed.
// Use accessor when you need multiple services temporarilyexport function myFunction(accessor: ServicesAccessor, arg: string): void { const logService = accessor.get(ILogService); const configService = accessor.get(IConfigurationService); const value = configService.getValue<string>('my.setting'); logService.info(`Processing ${arg} with ${value}`);}// Call via instantiation serviceinstantiationService.invokeFunction(myFunction, 'test-arg');