{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/9330","repository_url":"https://api.github.com/repos/dotnet/roslyn","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/9330/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/9330/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/9330/events","html_url":"https://github.com/dotnet/roslyn/issues/9330","id":137437997,"number":9330,"title":"C# Design Notes - catch up edition, Feb 29, 2016 (deconstruction and immutable object creation)","user":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/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"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Design%20Notes","name":"Design Notes","color":"fc2929"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-C%23","name":"Language-C#","color":"207de5"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Pattern%20Matching","name":"New Language Feature - Pattern Matching","color":"bfd4f2"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/New%20Language%20Feature%20-%20Records","name":"New Language Feature - Records","color":"bfd4f2"}],"state":"open","locked":false,"assignee":{"login":"MadsTorgersen","id":6677272,"avatar_url":"https://avatars.githubusercontent.com/u/6677272?v=3","gravatar_id":"","url":"https://api.github.com/users/MadsTorgersen","html_url":"https://github.com/MadsTorgersen","followers_url":"https://api.github.com/users/MadsTorgersen/followers","following_url":"https://api.github.com/users/MadsTorgersen/following{/other_user}","gists_url":"https://api.github.com/users/MadsTorgersen/gists{/gist_id}","starred_url":"https://api.github.com/users/MadsTorgersen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MadsTorgersen/subscriptions","organizations_url":"https://api.github.com/users/MadsTorgersen/orgs","repos_url":"https://api.github.com/users/MadsTorgersen/repos","events_url":"https://api.github.com/users/MadsTorgersen/events{/privacy}","received_events_url":"https://api.github.com/users/MadsTorgersen/received_events","type":"User","site_admin":false},"milestone":null,"comments":166,"created_at":"2016-03-01T01:43:02Z","updated_at":"2016-03-07T16:00:05Z","closed_at":null,"body":"# C# Language Design Notes Feb 29, 2016\r\n## Catch up edition (deconstruction and immutable object creation)\r\n\r\nOver the past couple of months various design activities took place that weren't documented in design notes. The following is a summary of the state of design regarding positional deconstruction, with-expressions and object initializers for immutable types.\r\n\r\n# Philosophy\r\n\r\nWe agree on the following design tenets:\r\n\r\nPositional deconstruction, with-expressions and object initializers are separable features, enabled by the presence of certain API patterns on types that can be expressed manually, as well as generated by other language features such as records.\r\n\r\n# API Patterns\r\n\r\nAPI patterns for a language feature facilitate two things:\r\n\r\n* Provide actual APIs to call _at runtime_ when the language feature is used\r\n* Inform the compiler _at compile time_ about how to generate code for the feature\r\n\r\nIt turns out the biggest design challenges are around the second part. Specifically, all these API patterns turn out to need to bridge between positional and name-based expressions of the members of types. How each API pattern does that is a central question of its design.\r\n\r\nAssume the following running example:\r\n\r\n``` c#\r\npublic class Person\r\n{\r\n  public string FirstName { get; }\r\n  public string LastName { get; }\r\n\r\n  public Person(string firstName, string lastName)\r\n  {\r\n    FirstName = firstName;\r\n    LastName = lastName;\r\n  }\r\n}\r\n```\r\n\r\nIn the following we'll consider extending and changing this type to expose various API patterns as we examine the individual language features.\r\n\r\nHere's an example of using the three language features:\r\n\r\n``` c#\r\nvar p = new Person { FirstName = \"Mickey\", LastName = \"Mouse\" }; // object initializer\r\nif (p is Person(\"Mickey\", *)) // positional deconstruction\r\n{\r\n  return p with { FirstName = \"Minney\" }; // with-expression\r\n}\r\n```\r\n\r\nSemantically this corresponds to something like this:\r\n\r\n``` c#\r\nvar p = new Person(\"Mickey\", \"Mouse\"); // constructor call\r\nif (p.FirstName == \"Mickey\") // property access\r\n{\r\n  return new Person(\"Minney\", p.LastName); // constructor call\r\n}\r\n```\r\n\r\nNotice how the new features that use property names correspond to API calls using positional parameters, whereas the feature that uses positions corresponds to member access by name!\r\n\r\n# Object initializers for immutable objects\r\n(See e.g. #229)\r\n\r\nThis feature allows an object initializer for which assignable properties are not found, to fall back to a constructor call taking the properties' new values as arguments.\r\n\r\n``` c#\r\nnew Person { FirstName = \"Mickey\", LastName = \"Mouse\" }\r\n```\r\nbecomes\r\n``` c#\r\nnew Person(\"Mickey\", \"Mouse\")\r\n```\r\n\r\nThe question then is: how does the compiler decide to pass the given FirstName as the first argument? Somehow it needs clues from the `Person` type as to which properties correspond to which constructor parameters. These clues cannot just be the constructor body: we need this to work across assemblies, so the clues must be evident from metadata.\r\n\r\nHere are some options:\r\n\r\n1: The type or constructor explicitly includes metadata for this purpose, e.g. in the form of attributes.\r\n2: The names of the constructor parameters must match exactly the names of the corresponding properties.\r\n\r\nThe former is unattractive because it requires the type's author to write those attributes. It requires the type to be explicitly edited for the purpose.\r\n\r\nThe latter is better in that it doesn't require extra API elements. However, API design guidelines stipulate that public properties start with uppercase, and parameters start with lower case. This pattern would break that, and for the same reason is highly unlikely to apply to any existing types.\r\n\r\nThis leads us to:\r\n\r\n3: The names of the constructor parameters must match the names of the corresponding properties, _modulo case_!\r\n\r\nThis would allow a large number of existing types to just work (including the example above), but at the cost of introducing case insensitivity to this part of the C# language. \r\n\r\n# With-expressions\r\n(see e.g. #5172)\r\n\r\nWith-expressions are similar to object initializers, except that they provide a source object from which to copy all the properties that aren't specified. Thus it seems reasonable to use a similar strategy for compilation; to call a constructor, this time filling in missing properties by accessing those on the source object.\r\n\r\nThus the same strategies as above would apply to establish the connection between properties and constructor parameters.\r\n\r\n``` c#\r\np with { FirstName = \"Minney\" }\r\n```\r\nbecomes\r\n``` c#\r\nnew Person(\"Minney\", p.LastName)\r\n```\r\n\r\nHowever, there's a hitch: if the runtime source object is actually of a derived type with more properties than are known from its static type, it would typically be expected that those are copied over too. In that case, the static type is also likely to be abstract (most base types are), so it wouldn't offer a callable constructor.\r\n\r\nFor this situation there needs to be a way that an abstract base class can offer \"with-ability\" that correctly copies over members of derived types. The best way we can think of is to offer a virtual `With` method, as follows:\r\n\r\n``` c#\r\npublic abstract class Person\r\n{\r\n  ...\r\n  public abstract Person With(string firstName, string lastName);\r\n}\r\n``` \r\n\r\nIn the presence of such a `With` method we would generate a with expression to call that instead of the constructor:\r\n\r\n``` c#\r\np.With(\"Minney\", p.LastName)\r\n```\r\n\r\nWe can decide whether to make with-expressions _require_ a `With` method, or fall back to constructor calls in its absence.\r\n\r\nIf we _require_ a `With` method, that makes for less interoperability with existing types. However, it gives us new opportunities for how to provide the position/name mapping metadata thorugh the declaration of that `With` method: For instance, we could introduce a new kind of default parameter that explicitly wires the parameter to a property:\r\n\r\n``` c#\r\n  public abstract Person With(string firstName = this.FirstName, string lastName = this.LastName);\r\n``` \r\n\r\nTo explicitly facilitate interop with an existing type, a mandatory `With` method could be allowed to be provided as an extension method. It is unclear how that would work with the default parameter approach, though.\r\n\r\n# Positional deconstruction\r\n(see e.g. #206)\r\n\r\nThis feature allows a positional syntax for extracting the property values from an object, for instance in the context of pattern matching, but potentially also elsewhere.\r\n\r\nIdeally, a positional deconstruction would simply generate an access of each member whose value is obtained:\r\n\r\n``` c#\r\np is Person(\"Mickey\", *)\r\n```\r\nbecomes\r\n``` c#\r\np.FirstName == \"Mickey\"\r\n```\r\n\r\nAgain, this requires the compiler's understanding of how positions correspond to property names. Again, the same strategies as for object initializers are possible. See e.g. #8415.\r\n\r\nAdditionally, just as in with-expressions, one might wish to override the default behavior, or provide it if names don't match. Again, an explicit method could be used:\r\n\r\n``` c#\r\npublic abstract class Person\r\n{\r\n  ...\r\n  public void Person GetValues(out string firstName, out string lastName);\r\n}\r\n```\r\nThere are several options as to the shape of such a method. Instead of out-parameters, it might return a tuple. This has pros and cons: there could be only one tuple-returning `GetValues` method, because there would be no parameters to distinguish signatures. This may be a good or a bad thing.\r\n\r\nJust as the `With` method, we can decide whether deconstruction should _require_ a `GetValues` method, or should fall back to metadata or to name matching against the constructor's parameter names.\r\n\r\nIf the `GetValues` method is used, the compiler doesn't need to resolve between positions and properties: the deconstruction as well as the method are already positional. We'd generate the code as follows:\r\n\r\n``` c#\r\nstring __p1;\r\nstring __p2;\r\np.GetValues(out __p1, out __p2);\r\n...\r\n__p1 == \"Mickey\"\r\n```\r\n\r\nSomewhat less elegant for sure, and possibly less efficient, since the `LastName` is obtained for no reason. However, this is compiler generated code that no one has to look at, and it can probably be optimized, so this may not be a big issue.\r\n\r\n# Summary\r\n\r\nFor each of these three features we are grappling with the position-to-property match. Our options:\r\n\r\n1. Require specific metadata\r\n2. Match property and parameter names, possibly in a case sensitive way\r\n3. For deconstruction and with-expressions, allow or require specific methods (`GetValues` and `With` respectively) to implement their behavior, and possibly have special syntax in `With` methods to provide the name-to-position matching.\r\n\r\nWe continue to work on this."},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/193314929","html_url":"https://github.com/dotnet/roslyn/issues/9330#issuecomment-193314929","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/9330","id":193314929,"user":{"login":"bbarry","id":84951,"avatar_url":"https://avatars.githubusercontent.com/u/84951?v=3","gravatar_id":"","url":"https://api.github.com/users/bbarry","html_url":"https://github.com/bbarry","followers_url":"https://api.github.com/users/bbarry/followers","following_url":"https://api.github.com/users/bbarry/following{/other_user}","gists_url":"https://api.github.com/users/bbarry/gists{/gist_id}","starred_url":"https://api.github.com/users/bbarry/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bbarry/subscriptions","organizations_url":"https://api.github.com/users/bbarry/orgs","repos_url":"https://api.github.com/users/bbarry/repos","events_url":"https://api.github.com/users/bbarry/events{/privacy}","received_events_url":"https://api.github.com/users/bbarry/received_events","type":"User","site_admin":false},"created_at":"2016-03-07T16:00:05Z","updated_at":"2016-03-07T16:00:05Z","body":"That said I am not sure that specific syntax is a great idea; only that it is a possible way to achieve the functionality."}}