{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/1207","labels_url":"https://api.github.com/repos/dotnet/roslyn/issues/1207/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/roslyn/issues/1207/comments","events_url":"https://api.github.com/repos/dotnet/roslyn/issues/1207/events","html_url":"https://github.com/dotnet/roslyn/issues/1207","id":60754646,"number":1207,"title":"C# Design Notes for Feb 11, 2015","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-Design%20Notes","name":"Area-Design Notes","color":"5319e7"},{"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/Language-C%23","name":"Language-C#","color":"207de5"},{"url":"https://api.github.com/repos/dotnet/roslyn/labels/Language-VB","name":"Language-VB","color":"207de5"}],"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":{"url":"https://api.github.com/repos/dotnet/roslyn/milestones/8","html_url":"https://github.com/dotnet/roslyn/milestones/C%23%207%20and%20VB%2015","labels_url":"https://api.github.com/repos/dotnet/roslyn/milestones/8/labels","id":962632,"number":8,"title":"C# 7 and VB 15","description":"","creator":{"login":"gafter","id":3804346,"avatar_url":"https://avatars.githubusercontent.com/u/3804346?v=3","gravatar_id":"","url":"https://api.github.com/users/gafter","html_url":"https://github.com/gafter","followers_url":"https://api.github.com/users/gafter/followers","following_url":"https://api.github.com/users/gafter/following{/other_user}","gists_url":"https://api.github.com/users/gafter/gists{/gist_id}","starred_url":"https://api.github.com/users/gafter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gafter/subscriptions","organizations_url":"https://api.github.com/users/gafter/orgs","repos_url":"https://api.github.com/users/gafter/repos","events_url":"https://api.github.com/users/gafter/events{/privacy}","received_events_url":"https://api.github.com/users/gafter/received_events","type":"User","site_admin":false},"open_issues":25,"closed_issues":0,"state":"open","created_at":"2015-02-02T23:41:34Z","updated_at":"2015-03-12T00:50:16Z","due_on":null,"closed_at":null},"comments":11,"created_at":"2015-03-12T00:50:16Z","updated_at":"2015-03-16T06:51:54Z","closed_at":null,"body":"C# Design Meeting Notes for Feb 11, 2015\r\n=========================================\r\n\r\n\r\nAgenda\r\n------\r\n\r\n1. Destructible types <*we recognize the problem but do not think this is quite the right solution for C#*>\r\n2. Tuples <*we like the proposal, but there are several things to iron out*>\r\n\r\n\r\n\r\n1. Destructible types\r\n=====================\r\n\r\nIssue #161 is a detailed proposal to add destructible types to C#.\r\n\r\nThe problem area is deterministic disposal of resources. \r\n\r\nFinalizers are notoriously non-deterministic (you can never know when or even whether they run), and rampant usage is quite a hit on performance. `using` statements are the language's current attempt at providing for deterministic disposal.\r\n\r\nThe problem with `using` statements is that no-one is forced to use them. An `IDisposable` resource is disposed of only if people remember to wrap it in a `using`. FxCop rules and now Roslyn diagnostics can be employed to help spot places where things aren't disposed that should be, but these have notoriously run into false positives and negatives, to the extent that they are a real nuisance and often get turned off.\r\n\r\nAt least one reason for this is the lack of their ability to track ownership. Whose responsibility is it to ultimately dispose the resource, and how is that responsibility transferred?\r\n\r\nDestructible types offer a sweeping approach to deal with this, that is a complete departure from `using` and `IDisposable` and instead leans more closely on C++-like RAII features.\r\n\r\nThe core idea is that destructible types are a language concept (they have to be declared as such):\r\n\r\n``` c#\r\ndestructible struct Handle\r\n{\r\n    IntPtr ptr;\r\n\r\n    ~Handle()\r\n    {\r\n        if (ptr != IntPtr.Zero) Free(ptr);\r\n    }\r\n}\r\n```\r\n\r\nDestructible types can only be fields in other destructible types.\r\n\r\nVariables of destructible type have their contents automatically disposed when they go out of scope.\r\n\r\nTo prevent multiple disposal, such variables can't just be assigned to others. Instead, ownership must be explicitly transferred (with a `move` keyword):\r\n\r\n``` c#\r\nvoid M()\r\n{\r\n    Handle h = CreateHandle();\r\n    Handle h2 = h; // Not allowed! Moves are explicit\r\n    Handle h3 = move h; // Explicit owner transfer\r\n    \r\n    M2(new Handle()); // ok, no owner\r\n    M2(h3); // Not allowed!\r\n    M2(move h3); // ok, explicit owner transfer\r\n\r\n}\r\nvoid M2(Handle h) ...\r\n```\r\n\r\nAt the end of M, all locals with destructible types are destructed, in opposite order of their introduction.\r\n\r\nAdditionally variables can be \"borrowed\" by being passed by `ref` to a method.\r\n\r\nThere are a lot of limitations to the feature, and it doesn't replace `IDisposable`, because you cannot use destructible types as type arguments, in arrays, and as fields of non-destructible types. Also, they cannot implement interfaces.\r\n\r\nAs a release valve one can have a `Box<T>` that \"magically\" transfers the ownership of a destructible type to the finalizer. The finalizer will crash the process if the destructible hasn't been properly destructed.\r\n\r\nThere has been some negative feedback around the destruction being implicit, even as the ownership transfer was explicit. When assignment overwrites a value, that value has to be destructed. But what about cases like this?\r\n\r\n``` c#\r\n{\r\n    D x;\r\n    if (e) x = new x();\r\n    x = new x();\r\n}\r\n```\r\n\r\nHow to know whether the second assignment overwrites a value, that should therefore be destructed? We'd have to keep track at runtime. Or prevent this situation through definitely assigned or unassigned rules, or the like.\r\n\r\n\r\nConclusion\r\n----------\r\n\r\nOverall, we see how this adds value, but not enough that having these concepts in the face of all C# developers. It doesn't earn back it's -100 points.\r\n\r\nCould we take a step back and have analyzers encourage practices that are less error prone? Yes. You just cannot have analyzers enforce correctness. And you cannot have them improve perf by affecting codegen. And they cannot do the heavy lifting for you.\r\n\r\nAn analyzer can help you with where to put your `using` statements. FxCop tries to do that today, but has too many false positives (use vs ownership). Even if you could get rid of those, there's a lot of code you'd need to write.\r\n\r\nPeople do struggle with `IDisposable` today. They don't know when things are `IDisposable`, whether they are the ones who should dispose things.\r\n\r\nWe would like to solve the problem in C#, and pick over this proposal for ideas, but we'd need to noodle with it to make it a better fit. It would need to integrate better with current `IDisposable`.\r\n\r\n\r\n\r\n2. Tuples\r\n=========\r\n\r\nIssue #347 is a proposal for adding tuples to C#. The main guiding principle for the proposal is to enable multiple return values, and wherever possible design them in analogy with parameter lists.\r\n\r\nAs such, a tuple type is a parenthesized list of types and names, just like a parameter list:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) { ... }\r\n\r\nvar t = Tally(myValues);\r\nConsole.WriteLine($\"Sum: {t.sum}, count: {t.count}\");  \r\n```\r\n\r\nAlong the same lines, tuple values can be constructed using a syntax similar to argument lists. By position:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) \r\n{\r\n    var sum = 0; var count = 0;\r\n    foreach (var value in values) { sum += value; count++; }\r\n    return (sum, count); // target typed to the return type\r\n}\r\n```\r\n\r\nOr by name:\r\n\r\n``` c#\r\npublic (int sum, int count) Tally(IEnumerable<int> values) \r\n{\r\n    var res = (sum: 0, count: 0); // infer tuple type from names and values\r\n    foreach (var value in values) { res.sum += value; res.count++; }\r\n    return res;\r\n}\r\n```\r\n\r\nFinally, there'd be syntax to deconstruct tuples into variables for the individual members:\r\n\r\n``` c#\r\n(var sum, var count) = Tally(myValues); // deconstruct result\r\nConsole.WriteLine($\"Sum: {sum}, count: {count}\");  \r\n```\r\n\r\nTuples should be thought of as temporary, ephemeral constellations of values without inherent conceptual connection: they just \"happen to be traveling together\".\r\n\r\nWe are generally eager to pursue this design. There are a number of open issues, that we explore in the following.\r\n\r\n\r\nType equivalence\r\n----------------\r\n\r\nIntuitively you'd expect that every time you use the same tuple type expression, it denotes the same type - that there is *structural equivalence*. However, depending on how we implement tuple types on top of the CLR, that may not always be easy to achieve - especially when unifying across different assemblies.\r\n\r\nTo take it to an extreme, what if they didn't even unify *within* an assembly? Even then they'd probably have significant value. They would be good for the \"ephemeral\" core scenario of things traveling together. It wouldn't let them serve as e.g. builders; they would really only work for the multiple return values scenario.\r\n\r\nEven then there'd be issues with virtual method overrides, etc: When you override and restate the return type, it doesn't really work if that declares a *different* return type.\r\n\r\n``` c#\r\nclass C\r\n{\r\n    public abstract (int x, int y) GetCoordinates();\r\n}\r\nclass D : C\r\n{\r\n    public override (int x, int y) GetCoordinates() { ... } // Error! different return type!!?!\r\n}\r\n```\r\n\r\nA middle ground is for these methods to unify *within* but not *across* assemblies. This is what we do for anonymous types today. However, there we are careful never to let the types occur in member signatures, whereas that would be the whole point of tuples.\r\n\r\nThis makes a little more sense than no unification at all, since at least all mentions of a tuple type within the same member body and even type body refer to the same type. However there'd still be weird behavior involving overrides across assembly boundaries, equivalence of generic types constructed with tuple types, etc. We could try to be smart about reusing types from other assemblies when possible, etc, but it would be quite a game of whack-a-mole to get all the edge cases behaving sensibly.\r\n\r\nThe only thing that really makes sense at the language level is true structural equivalence. Unfortunately, the CLR doesn't really provide for unification of types with similar member names across assemblies. Ideally we could *add* that to the CLR, but adding new functionality to the CLR is not something to be done lightly, as it breaks all downlevel targeting.\r\n\r\nA way to get structural equivalence working at compile time would be to encode tuples with framework types similar to the current `Tuple<...>` types. Member access would be compiled into access of members called `Item1` etc. The language level member names would be encoded in attributes for cross-assembly purposes:\r\n\r\n``` c#\r\nstruct VTuple2<T1, T2> { ... }\r\nstruct VTuple3<T1, T2, T3> { ... }\r\n\r\n/* Source: */\r\n(x: int, y: int) f() { ... }\r\n\r\n/* Generates: */\r\n[TupleNames(\"x\", \"y\")]\r\nVTuple2<int, int> f() {...}\r\n```\r\n\r\nUnfortunately this leaves problems at runtime: now there's *too much* type equivalence. If I want to do a runtime test to see if an object is an `(int x, int y)`, I would get false positives for an `(int a, int b)`, since the names wouldn't be part of the runtime type:\r\n\r\n``` c#\r\nobject o = (a: 7, b: 9);\r\nif (o is (int x, int y) t) WriteLine(t.x); // false positive\r\n```\r\n\r\nAlso the member names would be lost to `dynamic`:\r\n\r\n``` c#\r\ndynamic d = (a: 7, b: 9);\r\nWriteLine(t.a); // Error! Huh ??!?\r\n```\r\n\r\n\r\nOf course we could come up with tricks so that a tuple would carry some kind of member name info around at runtime, but not without cost. Or we could decide that the names are ephemeral, compile time only entities, kind of like parameter names, and losing them when boxing is actually a good thing. \r\n\r\nAlso, this approach means that languages that don't know about tuples would see the actual `Item1` member names when referencing member signatures containing tuples. Such languages include previous versions of C#! So for compatibility with those versions of C#, even in the new, tuple-aware, version, access through `Item1` etc would have to be still legal (though probably hidden from IntelliSense etc.).\r\n\r\nA nice thing about this scheme is that it leads to very little code bloat: it only relies on framework types, and not on compiler generated types in the assembly.\r\n\r\nThis remains an issue for further debate.\r\n\r\n\r\nConversions\r\n-----------\r\n\r\nThere are several kinds of conversions you can imagine allowing between tuple types:\r\n\r\n``` c#\r\n(string name, int age) t = (\"Johnny\", 29);\r\n\r\n/* Covariance */\r\n(object name, int age) t1 = t;\r\n\r\n/* Truncation */\r\n(object name) t2 = t;\r\n\r\n/* Reordering */\r\n(int age, string name) t3 = t;\r\n\r\n/* Renaming */\r\n(string n, int a) t4 = t;\r\n```\r\n\r\nNote that you cannot have both reordering and renaming in the language; we would have to choose.\r\n\r\nHowever, we are more inclined not to allow *any* of these conversions. They don't seem particularly helpful, and are likely to mask something you did wrong. People can always deconstruct and reconstruct if they want to get to a different tuple type, so our default position is to be super rigid here and only match exactly the same names and types in exactly the same order.\r\n\r\n\r\nDeconstruction\r\n--------------\r\n\r\nThere are a couple of issues with the proposed deconstruction syntax.\r\n\r\nFirst of all, since the recipient variables are on the left, there is no equivalent to parameter help guiding you as to what to name them. In that sense it would be better to have a syntax that receives the values on the right. But that seems an odd place to declare new variables that are intended to be in scope throughout the current block:\r\n\r\n``` c#\r\nTally(myValues) ~> (var sum, var count); // strawman right side alternative\r\nConsole.WriteLine($\"Sum: {sum}, count: {count}\");  \r\n```\r\n\r\nThat's not an actual proposed syntax, but just intended to show the point!\r\n\r\nA related issue is that you'd sometimes want to deconstruct into *existing* variables, not declare new ones:\r\n\r\n``` c#\r\n(sum, count) = Tally(myValues);\r\n```\r\n\r\nShould this be allowed? Is deconstruction then a declaration statement, an assignment statement or something different?\r\n\r\nFinally, once we have a deconstruction syntax, we'd probably want to enable it for other things than tuples. Maybe types can declare how they are to be deconstructed, in a way that the language understands. So deconstruction syntax should be considered in this broader context. For instance, if deconstructable types can be reference types, can deconstruction throw a null reference exception?\r\n\r\n\r\nLINQ\r\n----\r\n\r\nYou could imagine tuples becoming quite popular in LINQ queries, to the point of competing with anonymous types:\r\n\r\n``` c#\r\nfrom c in customers\r\nselect (name: c.Name, age: c.Age) into p\r\nwhere p.name == \"Kevin\"\r\nselect p;\r\n``` \r\n\r\nTo this end it would be useful for tuple \"literals\" to support *projection*, i.e. inference of member names, like anonymous types do:\r\n\r\n``` c#\r\nfrom c in customers\r\nselect (c.Name, c.Age) into p // infers (string Name, int Age)\r\nwhere p.Name == \"Kevin\"\r\nselect p;\r\n```\r\n\r\n\r\nOut parameters\r\n--------------\r\n\r\nF# allows out parameters to be seen as additional return values. We could consider something similar:\r\n\r\n``` c#\r\nbool TryGet(out int value){ ... }\r\n\r\n/* current style */\r\nint value;\r\nbool b = TryGet(out value);\r\n\r\n/* New style */\r\n(int value, bool b) = TryGet();\r\n```\r\n\r\n\r\nConclusion\r\n----------\r\n\r\nWe'd very much like to support tuples! There are a number of open questions, as well as interactions with other potential features. We'll want to keep debating and experimenting for a while before we lock down a design, to make sure we have the best overall story."},"comment":{"url":"https://api.github.com/repos/dotnet/roslyn/issues/comments/81454300","html_url":"https://github.com/dotnet/roslyn/issues/1207#issuecomment-81454300","issue_url":"https://api.github.com/repos/dotnet/roslyn/issues/1207","id":81454300,"user":{"login":"qrli","id":10579882,"avatar_url":"https://avatars.githubusercontent.com/u/10579882?v=3","gravatar_id":"","url":"https://api.github.com/users/qrli","html_url":"https://github.com/qrli","followers_url":"https://api.github.com/users/qrli/followers","following_url":"https://api.github.com/users/qrli/following{/other_user}","gists_url":"https://api.github.com/users/qrli/gists{/gist_id}","starred_url":"https://api.github.com/users/qrli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/qrli/subscriptions","organizations_url":"https://api.github.com/users/qrli/orgs","repos_url":"https://api.github.com/users/qrli/repos","events_url":"https://api.github.com/users/qrli/events{/privacy}","received_events_url":"https://api.github.com/users/qrli/received_events","type":"User","site_admin":false},"created_at":"2015-03-16T06:51:53Z","updated_at":"2015-03-16T06:51:53Z","body":"I agree with paulomorgado. Tuple should not have member names, to be consistent with other popular languages like F#, Python. In case where names are needed, you don't really need a tuple, but something like record class. \r\n\r\nWith names, e.g. (name: \"joe\", age: 20), it would be more of a dictionary, thus people may expect features for dictionaries, e.g. \r\n* foreach (KeyValuePair kv in someTuple)\r\n* JSONize tuple as {name: \"joe\", age: 20} instead of [\"joe\", 20]\r\n* Member-order-insensitive equivalence\r\n\r\nAlso, if C# used some custom implementation instead of System.Tuple<>, languages on .NET would be speaking different languages for tuple, which destroy interoperability. Designing tuple as another version of anonymous class is not a good choice."}}