{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/csharplang/issues/1690","repository_url":"https://api.github.com/repos/dotnet/csharplang","labels_url":"https://api.github.com/repos/dotnet/csharplang/issues/1690/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/csharplang/issues/1690/comments","events_url":"https://api.github.com/repos/dotnet/csharplang/issues/1690/events","html_url":"https://github.com/dotnet/csharplang/issues/1690","id":338018806,"node_id":"MDU6SXNzdWUzMzgwMTg4MDY=","number":1690,"title":"Proposal: Dependency Injection / Service Locator as a Language Feature","user":{"login":"dklompmaker","id":412269,"node_id":"MDQ6VXNlcjQxMjI2OQ==","avatar_url":"https://avatars2.githubusercontent.com/u/412269?v=4","gravatar_id":"","url":"https://api.github.com/users/dklompmaker","html_url":"https://github.com/dklompmaker","followers_url":"https://api.github.com/users/dklompmaker/followers","following_url":"https://api.github.com/users/dklompmaker/following{/other_user}","gists_url":"https://api.github.com/users/dklompmaker/gists{/gist_id}","starred_url":"https://api.github.com/users/dklompmaker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dklompmaker/subscriptions","organizations_url":"https://api.github.com/users/dklompmaker/orgs","repos_url":"https://api.github.com/users/dklompmaker/repos","events_url":"https://api.github.com/users/dklompmaker/events{/privacy}","received_events_url":"https://api.github.com/users/dklompmaker/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":57,"created_at":"2018-07-03T18:59:53Z","updated_at":"2018-07-04T13:22:42Z","closed_at":null,"author_association":"NONE","body":"# DI / Service Locator as Language Feature\r\nThe use of dependency injection frameworks has increased over the years; and for good reason. DI frameworks help to enforce good design patterns & principles. We have an opportunity to make CSharp the first language with IOC / DI as a language feature.\r\n\r\n**Proposal Version:** v0.1\r\n\r\n## Discussion\r\nThis proposal isn't perfect and would benefit from some deep discussion on the potential. I think this proposal can lead CSharp toward a language that supports good design practices as a feature.\r\n\r\n## Example Code\r\nBefore we dive into the details, let's take a high-level look at the implementation. In our example, we have a service and a consumer. Depending on the static / non-static binding, the objects are resolved on demand. Each object's dependencies are then injected via the container automatically.\r\n\r\n```csharp\r\npublic container AppContainer\r\n{\r\n    public static IHttpService as HttpService;\r\n    public ISomeType as SomeType;\r\n}\r\n\r\npublic class SomeType : ISomeType\r\n{\r\n    public IHttpService HttpService { get; set; }\r\n\r\n    public SomeType(inject IHttpService httpService)\r\n    {\r\n         HttpService = httpService;\r\n    }\r\n}\r\n\r\npublic class Program\r\n{\r\n     public static void Main(string[] args)\r\n     {\r\n          var httpService = IHttpService from AppContainer;\r\n          var someType = ISomeType from AppContainer;\r\n          var someType2 = ISomeType from AppContainer;\r\n         \r\n         // ISomeType is bound as a transient, therefore unique instances...        \r\n         Assert.NotEqual(someType, someType2);\r\n\r\n         // IHttpService is bound statically, therefore acts like a singleton...\r\n         Assert.AreEqual(httpServie, someType.HttpService);\r\n     }\r\n}\r\n```\r\n\r\n## Details\r\n\r\n### Inject Modifier\r\nAn inject modifier would make the parameter act like an optional value. If not provided, it would take default(T). The compiler would generate the variations with and without the values passed in.\r\n\r\n```csharp\r\npublic class SomeType : ISomeType\r\n{\r\n    public SomeType(inject IHttpService httpService) { }\r\n    public SomeType() {} // Generated...\r\n}\r\n\r\nvar someType = new SomeType(service);\r\nvar someType2 = new SomeType(); // inject treated as optional...\r\n```\r\n\r\nThere are cases where constructors cannot be used; For instance, in game engines like Unity3D. In these cases, we could support using the inject modifier on public properties or fields. Typically this is bad practice since it's harder to mock; however we can support these edge cases.\r\n\r\n```csharp\r\npublic class SomeOtherType : ISomeType\r\n{\r\n    public inject IHttpService HttpService { get; set; }\r\n}\r\n```\r\n\r\n### Container\r\nTypical DI frameworks define a `Container` that acts like a service locator. The language would introduce a new entity type called container.\r\n\r\n```csharp\r\npublic container AppContainer\r\n{\r\n     public static IHttpService as HttpService;\r\n     public virtual ISomeType as SomeType;\r\n}\r\n```\r\n\r\n### Container Inheritence\r\nInheriting other Containers would share the bindings between the two. Virtual bindings can be overridden just like properties or methods of a class.\r\n\r\n```csharp\r\npublic container OtherContainer : AppContainer\r\n{\r\n     public override ISomeType as OtherType;\r\n}\r\n```\r\n\r\n### Container Behavior Customization\r\nYou can customize the behavior of the Container by implementing a language provided interface.\r\n\r\n```csharp\r\npublic class MyResolveStrategy : IContainerResolveStrategy\r\n{\r\n    public TInstance Resolve<TInstance>(IResolveContext resolveContext) { ... }\r\n    public object Resolve(IResolveContext resolveContext) { ... }\r\n}\r\n\r\n[ContainerResolveStrategy<MyResolveStrategy>()]\r\npublic container AppContainer\r\n{\r\n    ...\r\n}\r\n```\r\n## Binding\r\nBinding types will be done through the container. \r\n\r\n### Binding Transient Types\r\n```csharp\r\npublic container AppContainer\r\n{\r\n    public ISomeType as SomeType;\r\n}\r\n```\r\n\r\n### Binding Singleton Types (Statics)\r\n```csharp\r\npublic container AppContainer\r\n{\r\n    public static IHttpService as HttpService;\r\n}\r\n```\r\n\r\n### Binding with custom factory\r\nSometimes you need to customize the creation of the types when you resolve. You can instead bind a factory method to the type. The parameters are evaluated and injected at the time of resolution.\r\n\r\n```csharp\r\npublic container AppContainer\r\n{\r\n    public static IHttpService(inject ISomeDependency someDependency) \r\n    {\r\n        return new HttpService(someDependency);\r\n    }\r\n}\r\n```\r\n\r\n### Binding types to the container at Runtime\r\nThis part needs work.\r\n```csharp\r\nvar instance = new SomeType();\r\nAppContainer.Bind<ISomeType>().To(instance);\r\n```\r\n\r\n### Runtime Resolving\r\nAs with traditional DI frameworks, the client can resolve a bound type. This would be supported in the language through syntactic sugar. \r\n\r\n```csharp\r\nvar someType = ISomeType from AppContainer;\r\nvar someType2 = ISomeType from OtherContainer;\r\n```\r\n### Default Interface Constructors\r\nInterfaces would have the ability to have default constructors that must be used by the implementing classes. The value of this can be seen in the next section.\r\n\r\n```csharp\r\npublic interface ISomeType\r\n{\r\n     ISomeType(inject IHttpService httpService);\r\n}\r\n```\r\n\r\n### Resolving with additional parameters\r\nSince we now allow for default constructors on the interfaces, we can pass through additional (non-injected) parameters at the time of resolving.\r\n\r\n```csharp\r\npublic interface ILogger\r\n{\r\n     ILogger(string logPath);\r\n}\r\n\r\npublic class FileLogger : ILogger\r\n{\r\n     public FileLogger(inject IFileWriter fileWriter, string logPath) : base.ILogger(logPath) { ... }\r\n     public ILogger(string logPath) {  ... }\r\n}\r\n\r\npublic container AppContainer\r\n{\r\n    public static ILogger as FileLogger;\r\n}\r\n... \r\n\r\nvar logger = ILogger(\"some/path/log.txt\") from AppContainer;\r\n```\r\n\r\n## Unit Testing\r\nSince the `inject` modifier is treated like an optional parameter, you still have the ability to pass in the dependencies through the constructor. Thus, mocking and unit-testing the classes is no different than how it is done today.\r\n\r\n```csharp\r\nvar someType = new SomeType(mockHttpService.Object);\r\n\r\nAssert.AreEqual(...);\r\n```"},"comment":{"url":"https://api.github.com/repos/dotnet/csharplang/issues/comments/402477891","html_url":"https://github.com/dotnet/csharplang/issues/1690#issuecomment-402477891","issue_url":"https://api.github.com/repos/dotnet/csharplang/issues/1690","id":402477891,"node_id":"MDEyOklzc3VlQ29tbWVudDQwMjQ3Nzg5MQ==","user":{"login":"iamkoch","id":792077,"node_id":"MDQ6VXNlcjc5MjA3Nw==","avatar_url":"https://avatars0.githubusercontent.com/u/792077?v=4","gravatar_id":"","url":"https://api.github.com/users/iamkoch","html_url":"https://github.com/iamkoch","followers_url":"https://api.github.com/users/iamkoch/followers","following_url":"https://api.github.com/users/iamkoch/following{/other_user}","gists_url":"https://api.github.com/users/iamkoch/gists{/gist_id}","starred_url":"https://api.github.com/users/iamkoch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/iamkoch/subscriptions","organizations_url":"https://api.github.com/users/iamkoch/orgs","repos_url":"https://api.github.com/users/iamkoch/repos","events_url":"https://api.github.com/users/iamkoch/events{/privacy}","received_events_url":"https://api.github.com/users/iamkoch/received_events","type":"User","site_admin":false},"created_at":"2018-07-04T13:22:42Z","updated_at":"2018-07-04T13:22:42Z","author_association":"NONE","body":"> Dependency injection as a language feature\r\nIsn't this just a constructor?\r\n\r\n> Service locator baked in\r\nI thought the whole service-locator-as-an-anti-pattern debate had long ago been put to bed\r\n\r\nKudos for the well thought-out code samples, but it's putting lipstick on a pig, and Service Locators and DI frameworks are solved problems which don't need to be first-class citizens."}}
{"actor":{"display_login":"iamkoch"}}