{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/7844","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/7844/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/7844/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/7844/events","html_url":"https://github.com/dotnet/roslyn/issues/7844","id":125510723,"number":7844,"title":"[Proposal][Roslyn] Add structural typing support","user":{"login":"glennblock","id":141124,"avatar_url":"https://avatars.githubusercontent.com/u/141124?v=3","gravatar_id":"","url":"https://api.github.com/users/glennblock","html_url":"https://github.com/glennblock","followers_url":"https://api.github.com/users/glennblock/followers","following_url":"https://api.github.com/users/glennblock/following{/other_user}","gists_url":"https://api.github.com/users/glennblock/gists{/gist_id}","starred_url":"https://api.github.com/users/glennblock/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glennblock/subscriptions","organizations_url":"https://api.github.com/users/glennblock/orgs","repos_url":"https://api.github.com/users/glennblock/repos","events_url":"https://api.github.com/users/glennblock/events{/privacy}","received_events_url":"https://api.github.com/users/glennblock/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Area-Language%20Design","name":"Area-Language Design","color":"5319e7"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":51,"created_at":"2016-01-07T23:45:10Z","updated_at":"2016-01-13T06:46:10Z","closed_at":null,"body":"This proposal is based on discussions with @MadsTorgersen and @DavidFowl. The design incorporates feedback from @crmckenzie and @edandersen.\r\n\r\nIn golang [interfaces](https://golang.org/ref/spec#Interface_types) are structural and implicit. Once an interface is declared, all go types that have the members defined in the interface present, automatically become implementers of that interface. This provides benefits for testability and extensibility. \r\n\r\nC# interfaces today require the implementer to explicitly implement the interface. C# does have the dynamic keyword however when you use dynamic, you lose all compile time checking / type safety. Interfaces in golang however allow retaining type safety while reducing type coupling (because the implemented does not have to explicitly implement the interface).\r\n\r\nThe proposal is to add structurally typed interface support to C#.\r\n\r\n## Overview\r\nHere is an example of how interfaces work in go.\r\n\r\n```go\r\n//Based on https://tour.golang.org/methods/5, but further simplified\r\ntype Writer interface {\r\n    Write(b []byte) (n int, err error)\r\n}\r\n\r\nfunc output(w Writer, msg string) {\r\n  ... \r\n}\r\n\r\nfunc main() {\r\n    var w Writer\r\n\r\n    // os.Stdout implements Writer\r\n    w = os.Stdout\r\n    output(w, \"Hello World\");\r\n}\r\n```\r\n\r\nThe user's code defines a `Writer` interface with a `Write` method. Next an `output` function is defined which accepts a `Writer`. Finally in the `main` function the code then creates a new `Writer` instance and sets the built in go `os.Stdout` object to that instance. The setting of the value succeeds though the `Stdout` object had no prior knowledge of the `Writer` interface.\r\n\r\nFrom a testability perspective this is extremely nice. If the user wants to test the `output` function, he/she can easily provide a mock `Writer`. It is not required to use any special mocking tools, nor do custom adapters and wrapper classes have to be created to wrap the native framework objects to make them testable. This will be beneficial for testing sealed classes, or non-virtual members.\r\n\r\nFrom an extensibility perspective this is also nice as new Writers can easily be plugged in simply by defining new types with the required members.\r\n\r\n## Proposal\r\nAllow add structural typing support for interfaces in .NET. Several variant approaches are proposed.\r\n\r\n### Benefits\r\n* Allows existing .NET classes to be more easily tested.\r\n* Implementers of libraries have to jump through less hoops to make their libraries testable.\r\n* Simplifies authoring of 3rd party extensions, removes the need for a shared library reference.\r\n* Enables new scenarios for anonymous types\r\n\r\n### Variant 1 - Structural keyword\r\nOne way to do this would be to introduce a new `structural` keyword which defines a structural interface. A `structural` is similar to a standard interface behavior-wise with one big exception, it is implicitly implemented by any class that has the members defined in the structural interface. The members of the interface can be any valid reference or value type.\r\n\r\n#### Example\r\nBelow is a sample C# snippet illustrating a scenario where this could be used. The example is more complicated than the go example for illustration of a real world pain point. It uses `System.Web.HttpRequest`, a class that today is often difficult to test against because it is sealed. Today testing is improved through helper/wrapper classes like `System.Web.HttpRequestBase` and `System.Web.HttpRequestWrapper`. These put a burden on frameworks to support these helpers and they add complexity.\r\n\r\nLet's assume a library has a method which performs validation against the current request.\r\n\r\n```csharp\r\npublic structural IHttpRequest() {\r\n    string UserAgent {get;}\r\n}\r\n\r\npublic class RequestValidator {\r\n    private IHttpRequest _req;\r\n    \r\n    public RequestValidator(IHttpRequest req) {\r\n        _req = req;    \r\n    }\r\n    \r\n    public void ValidateUserAgent() {\r\n        var ua = _req.UserAgent;\r\n        if (ua == null) {\r\n          throw new ArgumentException(\"UserAgent\", \"User Agent must be provided\");\r\n        }\r\n    }\r\n}\r\n\r\n//in prod code somenwhere\r\npublic class Handler {\r\n    public void HandleRequest(HttpContext context) {\r\n        var req = context.Request;\r\n        var validator = new Validator(req);\r\n        validator.ValidateUserAgent();\r\n    }\r\n}\r\n\r\n//unit test, checks to see the QueryString is accessed.\r\nusing Xunit;\r\nusing Should;\r\n\r\npublic class ValidatorTests {\r\n    [Fact]\r\n    public void ValidateUserAgentThrowsExceptionWhenUserAgentIsNull() {\r\n        var mock = new Mock<IHttpRequest>();\r\n        mock.SetupGet(r=>r.UserAgent).Returns(null);\r\n        var validator = new RequestValidator(mock.Object);\r\n        var ex = Assert.Throws<ArgumentException>(()=>validator.ValidateUserAgent());\r\n        ex.Message.ShouldEqual(\"User Agent must be provided\");  \r\n    }\r\n\r\n    [Fact]\r\n    public void ValidateUserAgentShouldSucceedWhenUserAgentIsNotNull() {\r\n        var mock = new Mock<IHttpRequest>();\r\n        mock.SetupGet(r=>r.UserAgent).Returns(\"foo\");\r\n        var validator = new RequestValidator(mock.Object);\r\n        validator.ValidateUserAgent();\r\n    }\r\n}\r\n```\r\n\r\nA few details about the code\r\n* `IHttpRequest` defines a structural interface. This was created by the library author and and is not part of the .NET framework.\r\n* `RequestValidator` is the system under test. It validates the UserAgent on the request and accepts an IHttpRequest instance.\r\n* `Handler` is a class deployed to production which passes in the current HttpContext.Request. HttpRequest becomes an implicit implementer of IHttpRequest as it matches on members.\r\n* The unit tests mocks the request using the structural interface in order to test the `Handler` class.\r\n* No wrapper or utility types are needed i.e. `HttpRequestBase`, `HttpRequestWrapper`.\r\n\r\nThe high level benefit illustrated here is being able to easily test the RequestValidator class though the types that it relies on themselves were not designed in a testable manner.  \r\n\r\n### Variant 2 - Structural attribute at the callsite (suggested by @crmckenzie) \r\nIn this variant the implementer of a method declares in the definition that one or more members are structurally matched. This is done via the useage of a new `[Structural]` attribute which is applied to a member that has an interface-type. Similar to Variant 1, all objects which have the members defined in the interface present, will match. \r\n\r\nNote: Once the instance has been received within the method, it can be set to a variable of the same interface type or passed as a parameter to a method that accepts a parameter of that interface whether it is marked structural or not. This is in order to remove the need to declare structural in a viral manner throughout the code.\r\n\r\n#### Example:\r\n\r\n```csharp\r\npublic IHttpRequest() {\r\n    string UserAgent {get;}\r\n}\r\n\r\npublic class RequestValidator {\r\n    //not necessary to be marked as Structural\r\n    private IHttpRequest _req;\r\n    \r\n    public RequestValidator([Structural] IHttpRequest req) {\r\n        _req = req;    \r\n    }\r\n    \r\n    public void ValidateUserAgent() {\r\n        var ua = _req.UserAgent;\r\n        if (ua == null) {\r\n          throw new ArgumentException(\"UserAgent\", \"User Agent must be provided\");\r\n        }\r\n    }\r\n}\r\n\r\n//in prod code somenwhere\r\npublic class Handler {\r\n    public void HandleRequest(HttpContext context) {\r\n        var req = context.Request;\r\n        var validator = new Validator(req);\r\n        validator.ValidateUserAgent();\r\n    }\r\n}\r\n\r\n//unit test, checks to see the QueryString is accessed.\r\nusing Xunit;\r\nusing Should;\r\n\r\npublic class ValidatorTests {\r\n    [Fact]\r\n    public void ValidateUserAgentThrowsExceptionWhenUserAgentIsNull() {\r\n        var mock = new Mock<IHttpRequest>();\r\n        mock.SetupGet(r=>r.UserAgent).Returns(null);\r\n        var validator = new RequestValidator(mock.Object);\r\n        var ex = Assert.Throws<ArgumentException>(()=>validator.ValidateUserAgent());\r\n        ex.Message.ShouldEqual(\"User Agent must be provided\");  \r\n    }\r\n\r\n    [Fact]\r\n    public void ValidateUserAgentShouldSucceedWhenUserAgentIsNotNull() {\r\n        var mock = new Mock<IHttpRequest>();\r\n        mock.SetupGet(r=>r.UserAgent).Returns(\"foo\");\r\n        var validator = new RequestValidator(mock.Object);\r\n        validator.ValidateUserAgent();\r\n    }\r\n}\r\n```\r\n\r\nDifferences between this code and Variant 1. \r\n* `IHttpRequest` defines a standard interface rather than a `Structural`. \r\n* `RequestValidator` defines the req parameter as `[Structural]`\r\n\r\n### Variant 3 - All interfaces are implicitly matched on structure, does not require any specific annotation or types. (suggested by @clipperhouse)\r\n\r\nWith this variant, the suggested functionality will just work without any special type or annotation required. \r\n\r\nAny object that matches an interface on structure will automatically implement that interface. This aligns closest to how go does it.\r\n\r\n## Questions\r\n* Q: Will general casting work i.e. I can cast `Foo` to `IFoo` simply because `Foo` has the members?\r\n* Q: Should the structural match be recursive? Meaning `StructuralA` can have params of type `StructuralB` and `StructuralC`?\r\n* Q: How does this work with the type system. What will `Type.GetInterfaces` return?\r\n\r\n## Notes\r\n* A completely diff approach is to allow defining the expected interface inline within the method using a declarative syntax. `Scala` supports [both](http://docs.scala-lang.org/style/types.html) approaches.\r\n"},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/171191545","html_url":"https://github.com/dotnet/roslyn/issues/7844#issuecomment-171191545","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/7844","id":171191545,"user":{"login":"glennblock","id":141124,"avatar_url":"https://avatars.githubusercontent.com/u/141124?v=3","gravatar_id":"","url":"https://api.github.com/users/glennblock","html_url":"https://github.com/glennblock","followers_url":"https://api.github.com/users/glennblock/followers","following_url":"https://api.github.com/users/glennblock/following{/other_user}","gists_url":"https://api.github.com/users/glennblock/gists{/gist_id}","starred_url":"https://api.github.com/users/glennblock/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/glennblock/subscriptions","organizations_url":"https://api.github.com/users/glennblock/orgs","repos_url":"https://api.github.com/users/glennblock/repos","events_url":"https://api.github.com/users/glennblock/events{/privacy}","received_events_url":"https://api.github.com/users/glennblock/received_events","type":"User","site_admin":false},"created_at":"2016-01-13T06:46:10Z","updated_at":"2016-01-13T06:46:10Z","body":"@cannorin That's interesting. I like 'as' better than an attribute.\nOn Tue, Jan 12, 2016 at 10:31 PM cannorin <notifications@github.com> wrote:\n\n> @glennblock <https://github.com/glennblock> Yes, this is what I want. The\n> point was that it is possible to satisfy the conditions required by\n> structural, unintentionally.\n>\n> Just an idea: extend as's meaning and implementation.\n>\n> interface IFoo { void DoSomething(); }class Bar { public void DoSomething() { } }void Baz(IFoo foo) { foo.DoSomething(); }\n>\n> Baz(new Bar() as IFoo); // <- typed structurally in compile-time\n>\n> —\n> Reply to this email directly or view it on GitHub\n> <https://github.com/dotnet/roslyn/issues/7844#issuecomment-171183472>.\n>\n"}}