Compare commits
807 commits
dev/jcouv/
...
main
Author | SHA1 | Date | |
---|---|---|---|
4df3296a0f | |||
9b15547365 | |||
87b42f24ad | |||
df1a9276ff | |||
3369227509 | |||
73077e88ca | |||
28efe56136 | |||
338b5908b6 | |||
e51570659f | |||
1f7a1340e1 | |||
b7f3a6653c | |||
d0176fe491 | |||
3514aabe7c | |||
2246988f5d | |||
26ccd64e79 | |||
43d9ed40c1 | |||
cb5871f620 | |||
6a76c1cf68 | |||
7bdf60f57a | |||
87137e67cc | |||
81f2670ec5 | |||
2e10a1b56d | |||
42b18c5d20 | |||
0f09d0ffdd | |||
5e25bf6e3a | |||
8f13ded863 | |||
2802e29f4c | |||
ec01d26c47 | |||
2ba7e98c33 | |||
9f54c9673f | |||
e8ddd37721 | |||
f20dd3076e | |||
95ffad807f | |||
ce40037cbf | |||
b6f2fb7598 | |||
8265129d86 | |||
90f617d0a7 | |||
dd9c089e9e | |||
762f5da1d2 | |||
e4ffa88c9f | |||
b8fa5f0689 | |||
d91984fe41 | |||
cf102bcbb2 | |||
a7aa8230e6 | |||
f548a2fea8 | |||
0130349385 | |||
7e2455955c | |||
6934787776 | |||
5675cc01cd | |||
a6737cff25 | |||
1bbea06217 | |||
eedd69b8f9 | |||
f5daa3c480 | |||
6e57bef953 | |||
5a4d8a5903 | |||
a012a07b58 | |||
0eb5952478 | |||
41f962b156 | |||
5c70a7acac | |||
40e8483671 | |||
0ea5665946 | |||
d52e29f186 | |||
c33a21734e | |||
138765d6b0 | |||
38794f2c5b | |||
17d51b5875 | |||
10f366592c | |||
33e3bfe31f | |||
088f20b6f9 | |||
fab06e432c | |||
4109c3d3de | |||
c1ccfd0d6a | |||
6f9ae4dccd | |||
5d5688cdac | |||
794e7fa1d9 | |||
e99dae65fc | |||
1ea4383736 | |||
c117a50a14 | |||
73dc536d4d | |||
076e25b596 | |||
ad321d2aac | |||
611b638273 | |||
8aaf7240da | |||
7c9a4c1946 | |||
cf4d452be2 | |||
f63f70960c | |||
8df1e5d5a7 | |||
fc125168ea | |||
fbbdf86e8a | |||
c0536b57c7 | |||
dcd6bda4b8 | |||
2f9862c8e6 | |||
93c42cf98b | |||
c8544ccc29 | |||
ff7e5f04d3 | |||
f9e8476c97 | |||
015894956e | |||
e85356bb95 | |||
b89d4c9340 | |||
e664481942 | |||
7fd92296bc | |||
86c641d4d5 | |||
a4c9db9a69 | |||
88825dd480 | |||
1d95494330 | |||
73af544295 | |||
8255f7d434 | |||
341d5184ba | |||
085024139e | |||
9a18b64732 | |||
6b0f5be210 | |||
cf9794dbb5 | |||
2329ddd55f | |||
134702a58a | |||
815cb6903b | |||
0e4b7363ec | |||
1714a1ef92 | |||
5b473bcf77 | |||
4907038e8e | |||
2c0481fb22 | |||
9a6e23e589 | |||
614edd88d7 | |||
1dbcfb485c | |||
dcbaa81525 | |||
1788bfd3ad | |||
9df76ef6c6 | |||
9f5c1d13c0 | |||
f4d1c13a6a | |||
d3c31779a1 | |||
f2fe67e0ef | |||
31f07a1839 | |||
fe65068f74 | |||
9c2ac8391c | |||
f05a7cd15f | |||
96e23022d4 | |||
1296da8e31 | |||
733f9611aa | |||
4f938f7595 | |||
136320cab1 | |||
7dc4311c7b | |||
7e863bee4a | |||
70fc3eb703 | |||
d91eab3660 | |||
e4edc94d14 | |||
a11816a462 | |||
6662672a2b | |||
b9ea94262d | |||
f4265f4899 | |||
22962db0e9 | |||
ef10c625c3 | |||
b518743c6f | |||
fb6af9155a | |||
592da35424 | |||
258cc959fd | |||
36b666765e | |||
3462d9cd28 | |||
ed6ad44e00 | |||
15c2bca0be | |||
3ca20d7107 | |||
85ed4b0785 | |||
95d0b48db5 | |||
20a57a9a6d | |||
b282f51ab9 | |||
c618b06a36 | |||
900e997f0b | |||
9cd6a7d4f7 | |||
cefa73b371 | |||
38845c5639 | |||
ecd0620606 | |||
2a17f2b214 | |||
4d2b699d44 | |||
c4debafa0c | |||
5a1112a255 | |||
5cb472d6b4 | |||
bcd1ace777 | |||
32f886de6b | |||
95d5ea7254 | |||
6c911c2b8b | |||
04cb45a8cb | |||
74a4b1da81 | |||
69be01c864 | |||
bb6eca3925 | |||
6137557e7e | |||
c2dfc38cbf | |||
b352c17312 | |||
e178fbbc93 | |||
a11db0a6d5 | |||
f751454623 | |||
f817e1f3db | |||
38c9773f13 | |||
46a2bcff0b | |||
76538e5e13 | |||
caf3fd7385 | |||
fb699c0c6d | |||
1a0e4bc758 | |||
3137df7cf4 | |||
03aedc0516 | |||
e6122ed487 | |||
e619b20e50 | |||
a3ff206420 | |||
555704e008 | |||
107d8df70d | |||
3c526ee195 | |||
fd0dc4f157 | |||
91752413d6 | |||
15c507ecc5 | |||
8777a189c7 | |||
eb30fa570e | |||
c4ee96110e | |||
ed8610b290 | |||
2b70bbed59 | |||
97ea12ccd5 | |||
bf7ce619e6 | |||
1f655b145c | |||
a4af8a0f8a | |||
cbc7d18430 | |||
f99f391ad9 | |||
b6f3504bc7 | |||
cc6cba159a | |||
e8b27ef36f | |||
99ee2e877f | |||
22a8d8976e | |||
5484a151f7 | |||
f58bfd7a0a | |||
2ea0826865 | |||
e1e8404fd8 | |||
2a35c5b299 | |||
2610effb3d | |||
9852346063 | |||
d8430957be | |||
f894bac818 | |||
5da3f4e8bf | |||
4e333cf6dc | |||
4cbfe08276 | |||
4f848c4496 | |||
9d8d98e61e | |||
8cb45218b0 | |||
e3e07b999b | |||
b40826529e | |||
0093f12358 | |||
a05ef31c01 | |||
b25408365a | |||
1ff26b634d | |||
3c8559f186 | |||
98d4638aaf | |||
e8f99b0bc3 | |||
5930dfb0d3 | |||
e01b11a71e | |||
1d59a69eff | |||
f6208cbf3f | |||
554b51929f | |||
ae52d973cd | |||
f1cbd4c9eb | |||
d41fcd0377 | |||
0e036c9129 | |||
d80953e14d | |||
d5434955d0 | |||
30fc08bc1e | |||
62cfaf1501 | |||
50d39a0057 | |||
b7091bad97 | |||
89724a1098 | |||
4865d92cfa | |||
cbfdc35dc8 | |||
6aeb218adc | |||
a8585351ff | |||
37885565fc | |||
9a0dddbf06 | |||
5d4ba55102 | |||
a01626f3de | |||
01fd6d3edf | |||
47143d1935 | |||
7d1157b445 | |||
56cbb1ed9a | |||
d194a55d68 | |||
9eb5a43bb4 | |||
0c48d101ae | |||
287db71b53 | |||
3dd985908e | |||
e374854c2c | |||
e3d4e5974d | |||
c80d21eab2 | |||
735692bbf2 | |||
5502b2bf7b | |||
790f2e01bc | |||
08fc429e4b | |||
a4c82ef11c | |||
dfd082f696 | |||
38c6ed3b57 | |||
9df5261be0 | |||
937a7130d6 | |||
b864efcce0 | |||
c3aa294baf | |||
72beb72f56 | |||
fffb1bfabe | |||
3ffbddf8e9 | |||
d910cfa1f4 | |||
8bddff3b2f | |||
95b26c4cb5 | |||
e4c820c211 | |||
3b89009e2c | |||
7f674675ab | |||
1b4f6ac09f | |||
ae69a53cf0 | |||
26827be206 | |||
5b6f535bee | |||
6fec6567a5 | |||
003c0606c3 | |||
5917736d40 | |||
6a48b62c9b | |||
b72a3c2bd2 | |||
8a5955168e | |||
00b0f04deb | |||
7397714ff4 | |||
b84678cf2c | |||
547b1d0991 | |||
14c7c290e9 | |||
0d06813349 | |||
f921de697a | |||
3f8f57e294 | |||
9196a30129 | |||
5a537b3d3d | |||
835828da4e | |||
f0590512a5 | |||
7d97a3f373 | |||
1a7ae36a02 | |||
8b3d79f62b | |||
93404fdb73 | |||
42c3677c51 | |||
834bcec4d5 | |||
6a3999caca | |||
0c0df35e90 | |||
00256069ad | |||
04d8e5c4d5 | |||
224a2ffa41 | |||
1f5b1dc19d | |||
766e12d62c | |||
5de77d1ea3 | |||
660f5a2e1c | |||
8afe00686b | |||
65fc81c8ed | |||
0156250878 | |||
e698abcbf0 | |||
1774292e88 | |||
ec31c2771e | |||
e7fd82ec95 | |||
6f3b971e45 | |||
efc800ced7 | |||
c968dc13da | |||
34da56b1df | |||
c449de91e9 | |||
1f038d2067 | |||
1741c519d2 | |||
2328664170 | |||
775dc37c18 | |||
6ab8409a32 | |||
6497983e4b | |||
a9b70c6ee1 | |||
4170bf5304 | |||
ebe33d5bfe | |||
0470255b81 | |||
f80973d27c | |||
6d6166fab1 | |||
0d1d5f2efe | |||
6aee98ef1f | |||
b7a4f850cb | |||
7199337452 | |||
a6ef4e93e4 | |||
aed221e8c3 | |||
e8a7683e85 | |||
b2d64b1570 | |||
6c631c0f39 | |||
06290c2f91 | |||
efac0d1e90 | |||
f2aaf44b9c | |||
388aae30b2 | |||
a0a6be9894 | |||
5c1dccd80e | |||
21d94ea8c2 | |||
cdce61573d | |||
73aec17c03 | |||
8cf85e8021 | |||
7d600fa568 | |||
3d2315ca49 | |||
41054950ef | |||
1eef8e0f64 | |||
05dacb663a | |||
59449140af | |||
1df9b32fd7 | |||
7c519e291f | |||
fd50ce8311 | |||
f5dd4c9e6a | |||
1fccd6246b | |||
38908f71e5 | |||
00d9d791b6 | |||
d636ada06f | |||
ed13b83b47 | |||
796742db0d | |||
4650fd6202 | |||
1cec9651fd | |||
29b0f41953 | |||
4c8b0a1c81 | |||
475c75bfab | |||
1458643eab | |||
29df547564 | |||
22ab7021c3 | |||
87c087f410 | |||
7712303fc6 | |||
7125a8428a | |||
b2b83eed29 | |||
a4c37c2d68 | |||
75ea1d741f | |||
ddbd1e2505 | |||
211d49a34f | |||
9807f9b2e2 | |||
4fc7fcf83b | |||
0d9762aa9f | |||
346d2d2125 | |||
1042196165 | |||
dc5830cd93 | |||
94b8189f4a | |||
e4b0ade065 | |||
a0b59a6768 | |||
8e7d390f6d | |||
f6a4820041 | |||
843230d5c0 | |||
87adc18115 | |||
2878f42415 | |||
c2b4f93666 | |||
88ef4b0056 | |||
d5d2f87c23 | |||
1efe320de3 | |||
41828d6f50 | |||
57ab7dca0d | |||
b22cb4c657 | |||
7cbb29465b | |||
7014365bd8 | |||
23c9e13493 | |||
6bfe301e79 | |||
a2cd3c4947 | |||
201a8cb7fe | |||
b5ff2efa01 | |||
a3d67d1415 | |||
77b2ab88a0 | |||
cd13c1328c | |||
3f6a61b29b | |||
8bcdc05af8 | |||
f11bd0833f | |||
74861d2aaf | |||
b2699bff42 | |||
3f901fa303 | |||
ad94ce8053 | |||
d6109bf586 | |||
b06bc10585 | |||
b6a06dae21 | |||
a87af899c0 | |||
3882702804 | |||
2661a4b395 | |||
fcf884f7f8 | |||
8203726f00 | |||
addae801a3 | |||
f20eb250f6 | |||
ca09fc178f | |||
b4b5b530da | |||
1333a5bcd3 | |||
2241fc9083 | |||
8032d0c7c3 | |||
1a6e8ca4e4 | |||
8cb28ad560 | |||
1b48340ac8 | |||
04ab4562e9 | |||
b3a10fc9ab | |||
622f4ea4ff | |||
9fb76a990c | |||
09aecc2f74 | |||
41a28555bf | |||
38c2e48ada | |||
8df4f29c1c | |||
d52960a47b | |||
b61ecce72b | |||
c3df20406f | |||
b23ef78ab5 | |||
7cf8d52977 | |||
cc11a3bb7a | |||
3a6c79012a | |||
95adf054b9 | |||
ac14d87c16 | |||
dd40959c46 | |||
31289a8255 | |||
3213aa0993 | |||
3e179c04d4 | |||
55df4c7f82 | |||
7ff6cb3bb7 | |||
94b0500e97 | |||
fc840b53c5 | |||
f38867ee6e | |||
e95770c463 | |||
d0fde6692c | |||
5d40e91f04 | |||
06ee75e6e3 | |||
cc02cda55f | |||
a69376ba67 | |||
a7b84ea928 | |||
eade6b9b55 | |||
db3bb77338 | |||
4f801a3b72 | |||
7d3c77ee79 | |||
f222935330 | |||
4f02d029d1 | |||
cd3c18237b | |||
a4a9e017df | |||
a88d56e313 | |||
29c08a3460 | |||
5bde862ee7 | |||
89bbdd101f | |||
71813f1643 | |||
c92ac50c37 | |||
eca37476cc | |||
94ed97a268 | |||
c92018c46b | |||
98a98559a8 | |||
dba9200990 | |||
0c25406d8a | |||
cede56944c | |||
3bdc3355b8 | |||
45bc98cca5 | |||
3f91ebca93 | |||
1d383e70d4 | |||
ea449ed112 | |||
28eb378734 | |||
1dd5317c8b | |||
2dda3cdd75 | |||
384dc3b466 | |||
c25bf006cd | |||
ebaabf6ca3 | |||
c11b561232 | |||
de1645446e | |||
c6f4a217a6 | |||
32bfa55d19 | |||
9a99808082 | |||
731b371161 | |||
84c16c14e0 | |||
1052c8855f | |||
994c41586e | |||
8087d27db1 | |||
a5899d45d1 | |||
6b9a732aa3 | |||
7d4c75a2da | |||
3402ccd0e0 | |||
66fd2ffbda | |||
1124abb051 | |||
fe73722354 | |||
e149d1e9c0 | |||
5a09cc1726 | |||
f1533732b0 | |||
c46030b7fd | |||
e874c6d46e | |||
db9bca1e5c | |||
c6c9e199ad | |||
09c67fbf78 | |||
ee1ba85e54 | |||
dfedf00f3c | |||
e4429f399f | |||
9cf2f66647 | |||
4e69e5cde3 | |||
78a7c37efe | |||
cc68af0b2a | |||
d0deb2f61b | |||
b50c5ee1d9 | |||
d96d221393 | |||
2cbd8db612 | |||
1aaa2ce941 | |||
9b736b5b73 | |||
c4f7854f1c | |||
0d8b6b6596 | |||
9ae50fad18 | |||
ad8e804774 | |||
43966001af | |||
5f42632905 | |||
ce26928a60 | |||
ec49f96cb7 | |||
0cab9c586a | |||
464e8d16e3 | |||
1bb454804d | |||
2ca2a15434 | |||
07582155a4 | |||
dd3261cfde | |||
413c55fe88 | |||
510a72d2fc | |||
6d8cee6e4a | |||
7460aa357e | |||
7058cc815e | |||
607706fbf5 | |||
6d43eec15c | |||
8498f4963c | |||
6f244db799 | |||
3b8500a93f | |||
f4b1117610 | |||
b901db48de | |||
aa2d828c82 | |||
7c44a62f9a | |||
26966202e8 | |||
0df3446c36 | |||
d48f35e584 | |||
0e365431d7 | |||
a05203115d | |||
eb00bb077e | |||
aa2ff4d18a | |||
9ca3758397 | |||
538df2e3c3 | |||
7efb8212e5 | |||
4c158751bb | |||
f84503e651 | |||
1a38cbd5b1 | |||
c90824ed24 | |||
82c4d68bb8 | |||
0f56445e25 | |||
3bfb6f875a | |||
0e42c53aa5 | |||
348d1c74a9 | |||
ab0c24d125 | |||
5c9b8f27bd | |||
956d7e8ece | |||
100d3f7f04 | |||
14f32ea1f1 | |||
6c404867b9 | |||
2b2452802a | |||
85263bfff8 | |||
41e377c7fa | |||
ae11413106 | |||
04f998e2f9 | |||
7f02ff0a8b | |||
e1f35d1569 | |||
35c14f7c15 | |||
300226c009 | |||
e355841daa | |||
751ebd4669 | |||
125539b88d | |||
c2df2ee72f | |||
9c3fde1e45 | |||
c2fe8f1d15 | |||
97eaba738e | |||
1466a8ae9e | |||
91b25c73a8 | |||
76631d3a84 | |||
9face9a8ec | |||
dccd6f151a | |||
ac0b1302b1 | |||
45d49ec640 | |||
a2d15ae55b | |||
c657de69f5 | |||
d073319400 | |||
fc347a5739 | |||
f7f8935d16 | |||
36bf39178d | |||
21a249d0d0 | |||
da3b99a86d | |||
a17f4c8ba8 | |||
c9ae01e03b | |||
347c6665f7 | |||
c583233dd8 | |||
e006b4808d | |||
7c75acb30b | |||
5ee18e113a | |||
8ca39632ed | |||
c30039481e | |||
0cec4e00c3 | |||
d77deaccf3 | |||
161e9facb5 | |||
b272718c5f | |||
af0265255f | |||
3159656400 | |||
124dabcb01 | |||
ab0873759f | |||
cbf73854a6 | |||
f1d43aade5 | |||
1281700df3 | |||
64566a1688 | |||
fea3e4f374 | |||
356ee04506 | |||
7f1c2a6fe4 | |||
5c7cc61921 | |||
3f177e90b1 | |||
95f5f86ba2 | |||
71a1696e3a | |||
a993c653f5 | |||
46a8659f06 | |||
6b14a23b9a | |||
2836e29605 | |||
da18b947b1 | |||
6901635c38 | |||
1ecebc412f | |||
bddb733d59 | |||
d414836632 | |||
583bdd220f | |||
47856e2351 | |||
0a75a38f7e | |||
907c155f09 | |||
c5428f507f | |||
88202acd40 | |||
52624f54c0 | |||
ff18bac738 | |||
017870d59c | |||
14cd563c27 | |||
7b3d402ade | |||
1873ddee96 | |||
7f0c8e4eac | |||
897bc11882 | |||
9143a30162 | |||
1cca522b9b | |||
18c41f4cea | |||
8003c3fe73 | |||
3d424e1a79 | |||
9b256c2906 | |||
59e3f278b9 | |||
7964497b5f | |||
d2cabfddb0 | |||
0286170d01 | |||
26342db77a | |||
21093514a3 | |||
1e1c7c72b1 | |||
75ddc88a40 | |||
64da1dcf00 | |||
23172a7c6d | |||
6e748b19f1 | |||
dee0aaf7b5 | |||
2fef08d7e0 | |||
74c38d2950 | |||
7ea0b8e328 | |||
1f45494dec | |||
194a043db7 | |||
40bf00abdf | |||
19eca6008a | |||
6ea9877330 | |||
1dbb8e82be | |||
cf22e016c7 | |||
636f053f74 | |||
c124903b80 | |||
9aa177443b | |||
d6718accc5 | |||
3fa28e4b54 | |||
45a1455732 | |||
a6c38525c2 | |||
cc9cb6eae6 | |||
a847159ec3 | |||
c9be90873a | |||
82e330685f | |||
33a60a1db1 | |||
59ad46e78c | |||
51e8d545c7 | |||
70a7286fac | |||
5688b13e66 | |||
cdbfdc555b | |||
f3170512e7 | |||
21b0400850 | |||
5745c18024 | |||
5278336b61 | |||
3cb286fe1d | |||
dfed65d105 | |||
9d90ddf5f6 | |||
9ab097ede9 | |||
f530e937c5 | |||
1922535987 | |||
2a6dffb607 | |||
86a6067e0e | |||
fdd049e944 | |||
526489cd5c | |||
fb5c260489 | |||
712ce8e53b | |||
5b388eb941 | |||
99094464ff | |||
b090ab8413 | |||
22c6fd0da6 | |||
36b028f4d6 | |||
8a0371d784 | |||
e5a56c4ff1 | |||
4914e41489 | |||
013d92093d | |||
73e2a7595b | |||
e0031c5139 | |||
21845d37fc | |||
2d521548ec | |||
3bbad41c9c | |||
f7fdb7cd7e | |||
19bc0f521e | |||
0b9e64a3c3 | |||
02b535d712 | |||
6f24703c82 | |||
d832b361bb | |||
e134bb7058 | |||
581270dfd2 | |||
f61a06970f | |||
1b98900380 | |||
b750272482 | |||
064e3b810a | |||
0513712252 | |||
6b335814c4 | |||
19d3d4ec87 | |||
919a3f22a0 | |||
c7c89c2e7b | |||
b2e7ad465c | |||
3843844601 | |||
162ba7ba85 | |||
0546ee1a16 | |||
2b894dc9aa | |||
a378a5f066 | |||
61967db208 |
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* @dotnet/roslyn-compiler
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Propose a language idea or ask a question
|
||||
url: https://github.com/dotnet/csharplang/discussions/new
|
||||
about: Starting with discussion is the way to create a new proposal.
|
50
.github/ISSUE_TEMPLATE/proposal_template.md
vendored
Normal file
50
.github/ISSUE_TEMPLATE/proposal_template.md
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
name: Create a language specification
|
||||
about: For proposals that have been invited by a team member.
|
||||
title: "[Proposal]: [FEATURE_NAME]"
|
||||
---
|
||||
<!--
|
||||
Hello, and thanks for your interest in contributing to C#! If you haven't been invited by a team member to open an issue, please instead open a discussion marked [draft issue] at https://github.com/dotnet/csharplang/discussions/new and we'll try to give you feedback on how to get to an issue-ready proposal.
|
||||
|
||||
New language feature proposals should fully fill out this template. This should include a complete detailed design, which describes the syntax of the feature, what that syntax means, and how it affects current parts of the spec. Please make sure to point out specific spec sections that need to be updated for this feature.
|
||||
-->
|
||||
# FEATURE_NAME
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: Not Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
<!-- One paragraph explanation of the feature. -->
|
||||
|
||||
## Motivation
|
||||
[motivation]: #motivation
|
||||
|
||||
<!-- Why are we doing this? What use cases does it support? What is the expected outcome? -->
|
||||
|
||||
## Detailed design
|
||||
[design]: #detailed-design
|
||||
|
||||
<!-- This is the bulk of the proposal. Explain the design in enough detail for somebody familiar with the language to understand, and for somebody familiar with the compiler to implement, and include examples of how the feature is used. Please include syntax and desired semantics for the change, including linking to the relevant parts of the existing C# spec to describe the changes necessary to implement this feature. An initial proposal does not need to cover all cases, but it should have enough detail to enable a language team member to bring this proposal to design if they so choose. -->
|
||||
|
||||
## Drawbacks
|
||||
[drawbacks]: #drawbacks
|
||||
|
||||
<!-- Why should we *not* do this? -->
|
||||
|
||||
## Alternatives
|
||||
[alternatives]: #alternatives
|
||||
|
||||
<!-- What other designs have been considered? What is the impact of not doing this? -->
|
||||
|
||||
## Unresolved questions
|
||||
[unresolved]: #unresolved-questions
|
||||
|
||||
<!-- What parts of the design are still undecided? -->
|
||||
|
||||
## Design meetings
|
||||
|
||||
<!-- Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. -->
|
6
CODE-OF-CONDUCT.md
Normal file
6
CODE-OF-CONDUCT.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Code of Conduct
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant
|
||||
to clarify expected behavior in our community.
|
||||
|
||||
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
|
|
@ -6,9 +6,13 @@
|
|||
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
- [Discord](https://aka.ms/csharp-discord) - Any discussion related to the C# language up to the application level.
|
||||
- [Dotnet Discord](https://aka.ms/dotnet-discord-csharp) - github.com/dotnet discord for discussing dotnet repositories (including csharplang).
|
||||
|
||||
[![Join the chat at https://aka.ms/csharp-discord](https://img.shields.io/discord/102860784329052160.svg)](https://aka.ms/csharp-discord)
|
||||
[![Chat on Discord](https://discordapp.com/api/guilds/143867839282020352/widget.png)](https://aka.ms/dotnet-discord-csharp)
|
||||
|
||||
- [C# Discord](https://aka.ms/csharp-discord) - General C# discussion not limited to the dotnet repositories.
|
||||
|
||||
[![Chat on Discord](https://discordapp.com/api/guilds/102860784329052160/widget.png)](https://aka.ms/csharp-discord)
|
||||
|
||||
- IRC - Any discussion related to the C# language up to the application level.
|
||||
|
||||
|
|
|
@ -1,109 +1,42 @@
|
|||
Features Added in C# Language Versions
|
||||
====================
|
||||
|
||||
# [C# 1.0](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#.NET_.282002.29) - Visual Studio .NET 2002
|
||||
# C# 10.0 - .NET 6 and Visual Studio 2022 version 17.0
|
||||
|
||||
- Classes
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Events
|
||||
- Properties
|
||||
- Delegates
|
||||
- Expressions
|
||||
- Statements
|
||||
- Attributes
|
||||
- Literals
|
||||
- [Record structs](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/record-structs.md) and `with` expressions on structs (`record struct Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
|
||||
- [Global using directives](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md): `global using` directives avoid repeating the same `using` directives across many files in your program.
|
||||
- [Improved definite assignment](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-definite-assignment.md): definite assignment and nullability analysis better handle common patterns such as `dictionary?.TryGetValue(key, out value) == true`.
|
||||
- [Constant interpolated strings](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/constant_interpolated_strings.md): interpolated strings composed of constants are themselves constants.
|
||||
- [Extended property patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/extended-property-patterns.md): property patterns allow accessing nested members (`if (e is MethodCallExpression { Method.Name: "MethodName" })`).
|
||||
- [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174): a record can inherit a base record with a sealed `ToString`.
|
||||
- [Incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md): improve the source generation experience in large projects by breaking down the source generation pipeline and caching intermediate results.
|
||||
- [Mixed deconstructions](https://github.com/dotnet/csharplang/issues/125): deconstruction-assignments and deconstruction-declarations can be blended together (`(existingLocal, var declaredLocal) = expression`).
|
||||
- [Method-level AsyncMethodBuilder](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md): the AsyncMethodBuilder used to compile an `async` method can be overridden locally.
|
||||
- [#line span directive](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/enhanced-line-directives.md): allow source generators like Razor fine-grained control of the line mapping with `#line` directives that specify the destination span (`#line (startLine, startChar) - (endLine, endChar) charOffset "fileName"`).
|
||||
- [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md): attributes and return types are allowed on lambdas; lambdas and method groups have a natural delegate type (`var f = short () => 1;`).
|
||||
- [Interpolated string handlers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md): interpolated string handler types allow efficient formatting of interpolated strings in assignments and invocations.
|
||||
- [File-scoped namespaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md): files with a single namespace don't need extra braces or indentation (`namespace X.Y.Z;`).
|
||||
- [Parameterless struct constructors](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md): support parameterless constructors and instance field initializers for struct types.
|
||||
- [CallerArgumentExpression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/caller-argument-expression.md): this attribute allows capturing the expressions passed to a method as strings.
|
||||
|
||||
# [C# 1.2](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12) - Visual Studio .NET 2003
|
||||
|
||||
- Dispose in foreach
|
||||
- foreach over string specialization
|
||||
|
||||
# [C# 2](https://msdn.microsoft.com/en-us/library/7cz8t42e(v=vs.80).aspx) - Visual Studio 2005
|
||||
- Generics
|
||||
- Partial types
|
||||
- Anonymous methods
|
||||
- Iterators
|
||||
- Nullable types
|
||||
- Getter/setter separate accessibility
|
||||
- Method group conversions (delegates)
|
||||
- Static classes
|
||||
- Delegate inference
|
||||
|
||||
# [C# 3](https://msdn.microsoft.com/en-us/library/bb308966.aspx) - Visual Studio 2008
|
||||
- Implicitly typed local variables
|
||||
- Object and collection initializers
|
||||
- Auto-Implemented properties
|
||||
- Anonymous types
|
||||
- Extension methods
|
||||
- Query expressions
|
||||
- Lambda expression
|
||||
- Expression trees
|
||||
- Partial methods
|
||||
|
||||
# [C# 4](https://msdn.microsoft.com/en-us/magazine/ff796223.aspx) - Visual Studio 2010
|
||||
- Dynamic binding
|
||||
- Named and optional arguments
|
||||
- Co- and Contra-variance for generic delegates and interfaces
|
||||
- Embedded interop types ("NoPIA")
|
||||
|
||||
# [C# 5](https://blogs.msdn.microsoft.com/mvpawardprogram/2012/03/26/an-introduction-to-new-features-in-c-5-0/) - Visual Studio 2012
|
||||
- Asynchronous methods
|
||||
- Caller info attributes
|
||||
|
||||
# [C# 6](https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6) - Visual Studio 2015
|
||||
- [Draft Specification online](https://github.com/dotnet/csharplang/blob/master/spec/README.md)
|
||||
- Compiler-as-a-service (Roslyn)
|
||||
- Import of static type members into namespace
|
||||
- Exception filters
|
||||
- Await in catch/finally blocks
|
||||
- Auto property initializers
|
||||
- Default values for getter-only properties
|
||||
- Expression-bodied members
|
||||
- Null propagator (null-conditional operator, succinct null checking)
|
||||
- String interpolation
|
||||
- nameof operator
|
||||
- Dictionary initializer
|
||||
|
||||
# [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) - Visual Studio 2017
|
||||
- [Out variables](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/out-var.md)
|
||||
- [Pattern matching](https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md)
|
||||
- [Tuples](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md)
|
||||
- [Deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md)
|
||||
- [Discards](https://github.com/dotnet/roslyn/blob/master/docs/features/discards.md)
|
||||
- [Local Functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/local-functions.md)
|
||||
- [Binary Literals](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/binary-literals.md)
|
||||
- [Digit Separators](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/digit-separators.md)
|
||||
- Ref returns and locals
|
||||
- [Generalized async return types](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)
|
||||
- More expression-bodied members
|
||||
- [Throw expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/throw-expression.md)
|
||||
|
||||
# [C# 7.1](https://blogs.msdn.microsoft.com/dotnet/2017/10/31/welcome-to-c-7-1/) - Visual Studio 2017 version 15.3
|
||||
- [Async main](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/async-main.md)
|
||||
- [Default expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md)
|
||||
- [Reference assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md)
|
||||
- [Inferred tuple element names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md)
|
||||
- [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/generics-pattern-match.md)
|
||||
|
||||
# [C# 7.2](https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/) - Visual Studio 2017 version 15.5
|
||||
- [Span and ref-like types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md)
|
||||
- [In parameters and readonly references](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md)
|
||||
- [Ref conditional](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/conditional-ref.md)
|
||||
- [Non-trailing named arguments](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md)
|
||||
- [Private protected accessibility](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/private-protected.md)
|
||||
- [Digit separator after base specifier](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/leading-separator.md)
|
||||
|
||||
# C# 7.3 - Visual Studio 2017 version 15.7
|
||||
- `System.Enum`, `System.Delegate` and [`unmanaged`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/blittable.md) constraints.
|
||||
- [Ref local re-assignment](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/ref-local-reassignment.md): Ref locals and ref parameters can now be reassigned with the ref assignment operator (`= ref`).
|
||||
- [Stackalloc initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/stackalloc-array-initializers.md): Stack-allocated arrays can now be initialized, e.g. `Span<int> x = stackalloc[] { 1, 2, 3 };`.
|
||||
- [Indexing movable fixed buffers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md): Fixed buffers can be indexed into without first being pinned.
|
||||
- [Custom `fixed` statement](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md): Types that implement a suitable `GetPinnableReference` can be used in a `fixed` statement.
|
||||
- [Improved overload candidates](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/improved-overload-candidates.md): Some overload resolution candidates can be ruled out early, thus reducing ambiguities.
|
||||
- [Expression variables in initializers and queries](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/expression-variables-in-initializers.md): Expression variables like `out var` and pattern variables are allowed in field initializers, constructor initializers and LINQ queries.
|
||||
- [Tuple comparison](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/tuple-equality.md): Tuples can now be compared with `==` and `!=`.
|
||||
- [Attributes on backing fields](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/auto-prop-field-attrs.md): Allows `[field: …]` attributes on an auto-implemented property to target its backing field.
|
||||
# C# 9.0 - .NET 5 and Visual Studio 2019 version 16.8
|
||||
- [Records](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md) and `with` expressions: succinctly declare reference types with value semantics (`record Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
|
||||
- [Init-only setters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/init.md): init-only properties can be set during object creation (`int Property { get; init; }`).
|
||||
- [Top-level statements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/top-level-statements.md): the entry point logic of a program can be written without declaring an explicit type or `Main` method.
|
||||
- [Pattern matching enhancements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/patterns3.md): relational patterns (`is < 30`), combinator patterns (`is >= 0 and <= 100`, `case 3 or 4:`, `is not null`), parenthesized patterns (`is int and (< 0 or > 100)`), type patterns (`case Type:`).
|
||||
- [Native sized integers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/native-integers.md): the numeric types `nint` and `nuint` match the platform memory size.
|
||||
- [Function pointers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/function-pointers.md): enable high-performance code leveraging IL instructions `ldftn` and `calli` (`delegate* <int, void> local;`)
|
||||
- [Suppress emitting `localsinit` flag](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/skip-localsinit.md): attributing a method with `[SkipLocalsInit]` will suppress emitting the `localsinit` flag to reduce cost of zero-initialization.
|
||||
- [Target-typed new expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/target-typed-new.md): `Point p = new(42, 43);`.
|
||||
- [Static anonymous functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/static-anonymous-functions.md): ensure that anonymous functions don't capture `this` or local variables (`static () => { ... };`).
|
||||
- [Target-typed conditional expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/target-typed-conditional-expression.md): conditional expressions which lack a natural type can be target-typed (`int? x = b ? 1 : null;`).
|
||||
- [Covariant return types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/covariant-returns.md): a method override on reference types can declare a more derived return type.
|
||||
- [Lambda discard parameters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/lambda-discard-parameters.md): multiple parameters `_` appearing in a lambda are allowed and are discards.
|
||||
- [Attributes on local functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/local-function-attributes.md).
|
||||
- [Module initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/module-initializers.md): a method attributed with `[ModuleInitializer]` will be executed before any other code in the assembly.
|
||||
- [Extension `GetEnumerator`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/extension-getenumerator.md): an extension `GetEnumerator` method can be used in a `foreach`.
|
||||
- [Partial methods with returned values](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/extending-partial-methods.md): partial methods can have any accessibility, return a type other than `void` and use `out` parameters, but must be implemented.
|
||||
- [Source Generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)
|
||||
|
||||
# C# 8.0 - .NET Core 3.0 and Visual Studio 2019 version 16.3
|
||||
- [Nullable reference types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/nullable-reference-types-specification.md): express nullability intent on reference types with `?`, `notnull` constraint and annotations attributes in APIs, the compiler will use those to try and detect possible `null` values being dereferenced or passed to unsuitable APIs.
|
||||
|
@ -120,3 +53,121 @@ Features Added in C# Language Versions
|
|||
- [Alternative interpolated verbatim strings](https://github.com/dotnet/csharplang/issues/1630): `@$"..."` strings are recognized as interpolated verbatim strings just like `$@"..."`.
|
||||
- [Obsolete on property accessors](https://github.com/dotnet/csharplang/issues/2152): property accessors can now be individually marked as obsolete.
|
||||
- [Permit `t is null` on unconstrained type parameter](https://github.com/dotnet/csharplang/issues/1284)
|
||||
|
||||
# C# 7.3 - Visual Studio 2017 version 15.7
|
||||
- `System.Enum`, `System.Delegate` and [`unmanaged`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/blittable.md) constraints.
|
||||
- [Ref local re-assignment](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/ref-local-reassignment.md): Ref locals and ref parameters can now be reassigned with the ref assignment operator (`= ref`).
|
||||
- [Stackalloc initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/stackalloc-array-initializers.md): Stack-allocated arrays can now be initialized, e.g. `Span<int> x = stackalloc[] { 1, 2, 3 };`.
|
||||
- [Indexing movable fixed buffers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md): Fixed buffers can be indexed into without first being pinned.
|
||||
- [Custom `fixed` statement](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md): Types that implement a suitable `GetPinnableReference` can be used in a `fixed` statement.
|
||||
- [Improved overload candidates](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/improved-overload-candidates.md): Some overload resolution candidates can be ruled out early, thus reducing ambiguities.
|
||||
- [Expression variables in initializers and queries](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/expression-variables-in-initializers.md): Expression variables like `out var` and pattern variables are allowed in field initializers, constructor initializers and LINQ queries.
|
||||
- [Tuple comparison](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/tuple-equality.md): Tuples can now be compared with `==` and `!=`.
|
||||
- [Attributes on backing fields](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/auto-prop-field-attrs.md): Allows `[field: …]` attributes on an auto-implemented property to target its backing field.
|
||||
|
||||
# [C# 7.2](https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/) - Visual Studio 2017 version 15.5
|
||||
- [Span and ref-like types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md)
|
||||
- [In parameters and readonly references](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md)
|
||||
- [Ref conditional](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/conditional-ref.md)
|
||||
- [Non-trailing named arguments](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md)
|
||||
- [Private protected accessibility](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/private-protected.md)
|
||||
- [Digit separator after base specifier](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/leading-separator.md)
|
||||
|
||||
# [C# 7.1](https://blogs.msdn.microsoft.com/dotnet/2017/10/31/welcome-to-c-7-1/) - Visual Studio 2017 version 15.3
|
||||
- [Async main](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/async-main.md)
|
||||
- [Default expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md)
|
||||
- [Reference assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md)
|
||||
- [Inferred tuple element names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md)
|
||||
- [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/generics-pattern-match.md)
|
||||
|
||||
# [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) - Visual Studio 2017
|
||||
- [Out variables](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/out-var.md)
|
||||
- [Pattern matching](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/pattern-matching.md)
|
||||
- [Tuples](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md)
|
||||
- [Deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md)
|
||||
- [Discards](https://github.com/dotnet/roslyn/blob/master/docs/features/discards.md)
|
||||
- [Local Functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/local-functions.md)
|
||||
- [Binary Literals](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/binary-literals.md)
|
||||
- [Digit Separators](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/digit-separators.md)
|
||||
- [Ref returns and locals](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/ref-returns)
|
||||
- [Generalized async return types](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)
|
||||
- [More expression-bodied members](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
|
||||
- [Throw expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/throw-expression.md)
|
||||
|
||||
# [C# 6](https://github.com/dotnet/roslyn/blob/master/docs/wiki/New-Language-Features-in-C%23-6.md) - Visual Studio 2015
|
||||
- [Draft Specification online](https://github.com/dotnet/csharplang/blob/master/spec/README.md)
|
||||
- Compiler-as-a-service (Roslyn)
|
||||
- [Import of static type members into namespace](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-static)
|
||||
- [Exception filters](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/when)
|
||||
- Await in catch/finally blocks
|
||||
- Auto property initializers
|
||||
- Default values for getter-only properties
|
||||
- [Expression-bodied members](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
|
||||
- Null propagator (null-conditional operator, succinct null checking)
|
||||
- [String interpolation](https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated)
|
||||
- [nameof operator](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/nameof)
|
||||
- Dictionary initializer
|
||||
|
||||
# [C# 5](https://blogs.msdn.microsoft.com/mvpawardprogram/2012/03/26/an-introduction-to-new-features-in-c-5-0/) - Visual Studio 2012
|
||||
- [Asynchronous methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/async/)
|
||||
- [Caller info attributes](https://docs.microsoft.com/dotnet/csharp/language-reference/attributes/caller-information)
|
||||
- foreach loop was changed to generates a new loop variable rather than closing over the same variable every time
|
||||
|
||||
# [C# 4](https://msdn.microsoft.com/magazine/ff796223.aspx) - Visual Studio 2010
|
||||
- [Dynamic binding](https://docs.microsoft.com/dotnet/csharp/programming-guide/types/using-type-dynamic)
|
||||
- [Named and optional arguments](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments)
|
||||
- [Co- and Contra-variance for generic delegates and interfaces](https://docs.microsoft.com/dotnet/standard/generics/covariance-and-contravariance)
|
||||
- [Embedded interop types ("NoPIA")](https://docs.microsoft.com/dotnet/framework/interop/type-equivalence-and-embedded-interop-types)
|
||||
|
||||
# [C# 3](https://msdn.microsoft.com/library/bb308966.aspx) - Visual Studio 2008
|
||||
- [Implicitly typed local variables](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/implicitly-typed-local-variables)
|
||||
- [Object and collection initializers](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers)
|
||||
- [Auto-Implemented properties](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties)
|
||||
- [Anonymous types](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types)
|
||||
- [Extension methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/extension-methods)
|
||||
- [Query expressions, a.k.a LINQ (Language Integrated Query)](https://docs.microsoft.com/dotnet/csharp/linq/query-expression-basics)
|
||||
- [Lambda expression](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions)
|
||||
- [Expression trees](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/expression-trees/)
|
||||
- [Partial methods](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/partial-method)
|
||||
- [Lock statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/lock-statement)
|
||||
|
||||
# [C# 2](https://msdn.microsoft.com/library/7cz8t42e(v=vs.80).aspx) - Visual Studio 2005
|
||||
- [Generics](https://docs.microsoft.com/dotnet/csharp/programming-guide/generics/)
|
||||
- [Partial types](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/partial-type)
|
||||
- [Anonymous methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions)
|
||||
- [Iterators, a.k.a yield statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/yield)
|
||||
- [Nullable types](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/nullable-value-types)
|
||||
- Getter/setter separate accessibility
|
||||
- Method group conversions (delegates)
|
||||
- [Static classes](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members)
|
||||
- Delegate inference
|
||||
- Type and namespace aliases
|
||||
- [Covariance and contravariance](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/covariance-contravariance/)
|
||||
|
||||
# [C# 1.2](https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-version-history#c-version-12) - Visual Studio .NET 2003
|
||||
- Dispose in foreach
|
||||
- foreach over string specialization
|
||||
|
||||
# [C# 1.0](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#.NET_.282002.29) - Visual Studio .NET 2002
|
||||
- [Classes](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/classes)
|
||||
- [Structs](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/struct)
|
||||
- [Enums](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/enum)
|
||||
- [Interfaces](https://docs.microsoft.com/dotnet/csharp/programming-guide/interfaces/)
|
||||
- [Events](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/event)
|
||||
- [Operator overloading](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/operator-overloading)
|
||||
- [User-defined conversion operators](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/user-defined-conversion-operators)
|
||||
- [Properties](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/properties)
|
||||
- [Indexers](https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/)
|
||||
- Output parameters ([out](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/out) and [ref](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/ref))
|
||||
- [`params` arrays](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/params)
|
||||
- [Delegates](https://docs.microsoft.com/dotnet/csharp/programming-guide/delegates/)
|
||||
- Expressions
|
||||
- [using statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-statement)
|
||||
- [goto statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/goto)
|
||||
- [Preprocessor directives](https://docs.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives/)
|
||||
- [Unsafe code and pointers](https://docs.microsoft.com/dotnet/csharp/programming-guide/unsafe-code-pointers/)
|
||||
- [Attributes](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/)
|
||||
- Literals
|
||||
- [Verbatim identifier](https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/verbatim)
|
||||
- Unsigned integer types
|
||||
- [Boxing and unboxing](https://docs.microsoft.com/dotnet/csharp/programming-guide/types/boxing-and-unboxing)
|
||||
|
|
35
README.md
35
README.md
|
@ -1,6 +1,6 @@
|
|||
# C# Language Design
|
||||
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Chat on Discord](https://discordapp.com/api/guilds/143867839282020352/widget.png)](https://aka.ms/dotnet-discord-csharp)
|
||||
|
||||
Welcome to the official repo for C# language design. This is where new C# language features are developed, adopted and specified.
|
||||
|
||||
|
@ -17,23 +17,29 @@ If you discover bugs or deficiencies in the above, please leave an issue to rais
|
|||
|
||||
For *new feature proposals*, however, please raise them for [discussion](https://github.com/dotnet/csharplang/labels/Discussion), and *only* submit a proposal as a pull request if invited to do so by a member of the Language Design Team (a "champion").
|
||||
|
||||
## Discussion
|
||||
## Discussions
|
||||
|
||||
Discussion pertaining to language features takes place in the form of issues in this repo, under the [Discussion label](https://github.com/dotnet/csharplang/labels/Discussion).
|
||||
Debate pertaining to language features takes place in the form of [Discussions](https://github.com/dotnet/csharplang/discussions) in this repo.
|
||||
|
||||
If you want to suggest a feature, discuss current design notes or proposals, etc., please [open a new issue](https://github.com/dotnet/csharplang/issues/new), and it will be tagged Discussion.
|
||||
If you want to suggest a feature, discuss current design notes or proposals, etc., please [open a new Discussion topic](https://github.com/dotnet/csharplang/discussions/new).
|
||||
|
||||
GitHub is not ideal for discussions, but it is beneficial to have language features discussed nearby to where the design artifacts are. Comment threads that are short and stay on topic are much more likely to be read. If you leave comment number fifty, chances are that only a few people will read it. To make discussions easier to navigate and benefit from, please observe a few rules of thumb:
|
||||
Discussions that are short and stay on topic are much more likely to be read. If you leave comment number fifty, chances are that only a few people will read it. To make discussions easier to navigate and benefit from, please observe a few rules of thumb:
|
||||
|
||||
- Discussion should be relevant to C# language design. Issues that are not will be summarily closed.
|
||||
- Choose a descriptive title for the issue, that clearly communicates the scope of discussion.
|
||||
- Stick to the topic of the issue title. If a comment is tangential, start a new issue and link back.
|
||||
- If a comment goes into detail on a subtopic, also consider starting a new issue and linking back.
|
||||
- Discussion should be relevant to C# language design. If they are not, they will be summarily closed.
|
||||
- Choose a descriptive topic that clearly communicates the scope of discussion.
|
||||
- Stick to the topic of the discussion. If a comment is tangential, or goes into detail on a subtopic, start a new discussion and link back.
|
||||
- Is your comment useful for others to read, or can it be adequately expressed with an emoji reaction to an existing comment?
|
||||
|
||||
Language proposals which prevent specific syntax from occurring can be achieved with [a Roslyn analyzer](https://docs.microsoft.com/en-us/visualstudio/extensibility/getting-started-with-roslyn-analyzers). Proposals that only make existing syntax optionally illegal will be rejected by the language design committee to prevent increased language complexity.
|
||||
|
||||
## Proposals
|
||||
Once you have a fully fleshed out proposal describing a new language feature in syntactic and semantic detail, please [open an issue for it](https://github.com/dotnet/csharplang/issues/new/choose), and it will be labeled as a [Proposal](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3AProposal). The comment thread on the issue can be used to hash out or briefly discuss details of the proposal, as well as pros and cons of adopting it into C#. If an issue does not meet the bar of being a full proposal, we may move it to a discussion, so that it can be "baked" further. Specific open issues or more expansive discussion with a proposal will often warrant opening a side discussion rather than cluttering the comment section on the issue.
|
||||
|
||||
When a member of the C# LDM finds that a proposal merits discussion, they can [Champion](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22) it, which means that they will bring it to the C# Language Design Meeting. If the LDM decides to work on adopting the feature, the proposer, the champion and others can collaborate on adding it as a document to the [Proposals](proposals) folder, where its evolution can be tracked over time.
|
||||
|
||||
## Design Process
|
||||
|
||||
[Proposals](proposals) are raised by, or on invitation from, "champions" on the LDT. They evolve as a result of decisions in [Language Design Meetings](meetings), which are informed by [discussion](https://github.com/dotnet/csharplang/labels/Discussion), experiments, and offline design work.
|
||||
[Proposals](proposals) evolve as a result of decisions in [Language Design Meetings](meetings), which are informed by [discussions](https://github.com/dotnet/csharplang/discussions), experiments, and offline design work.
|
||||
|
||||
In many cases it will be necessary to implement and share a prototype of a feature in order to land on the right design, and ultimately decide whether to adopt the feature. Prototypes help discover both implementation and usability issues of a feature. A prototype should be implemented in a fork of the [Roslyn repo](https://github.com/dotnet/roslyn) and meet the following bar:
|
||||
|
||||
|
@ -45,6 +51,15 @@ Once approved, a feature should be fully implemented in [Roslyn](https://github.
|
|||
|
||||
**DISCLAIMER**: An active proposal is under active consideration for inclusion into a future version of the C# programming language but is not in any way guaranteed to ultimately be included in the next or any version of the language. A proposal may be postponed or rejected at any time during any phase of the above process based on feedback from the design team, community, code reviewers, or testing.
|
||||
|
||||
### Milestones
|
||||
|
||||
We have a few different milestones for issues on the repo:
|
||||
* [Working Set](https://github.com/dotnet/csharplang/milestone/19) is the set of championed proposals that are currently being actively worked on. Not everything in this milestone will make the next version of C#, but it will get design time during the upcoming release.
|
||||
* [Backlog](https://github.com/dotnet/csharplang/milestone/10) is the set of championed proposals that have been triaged, but are not being actively worked on. While discussion and ideas from the community are welcomed on these proposals, the cost of the design work and implementation review on these features are too high for us to consider community implementation until we are ready for it.
|
||||
* [Any Time](https://github.com/dotnet/csharplang/milestone/14) is the set of championed proposals that have been triaged, but are not being actively worked on and are open to community implementation. Issues in this can be in one of 2 states: needs approved specification, and needs implementation. Those that need a specification still need to be presented during LDM for approval of the spec, but we are willing to take the time to do so at our earliest convenience.
|
||||
* [Likely Never](https://github.com/dotnet/csharplang/milestone/13) is the set of proposals that the LDM has rejected from the language. Without strong need or community feedback, these proposals will not be considered in the future.
|
||||
* Numbered milestones are the set of features that have been implemented for that particular language version. For closed milestones, these are the set of things that shipped with that release. For open milestones, features can be potentially pulled later if we discover compatability or other issues as we near release.
|
||||
|
||||
## Language Design Meetings
|
||||
|
||||
Language Design Meetings (LDMs) are held by the LDT and occasional invited guests, and are documented in Design Meeting Notes in the [meetings](meetings) folder, organized in folders by year. The lifetime of a design meeting note is described in [meetings/README.md](meetings/README.md). LDMs are where decisions about future C# versions are made, including which proposals to work on, how to evolve the proposals, and whether and when to adopt them.
|
||||
|
|
|
@ -274,7 +274,7 @@ A value-semantics class like the above would be automatically generated by a "re
|
|||
class Point(int X, int Y);
|
||||
```
|
||||
|
||||
By default, this would generate all of the above, except parameter names would be upper case. If you want to supercede default behavior, you can give it a body and do that explicitly. For instance, you could make X mutable:
|
||||
By default, this would generate all of the above, except parameter names would be upper case. If you want to supersede default behavior, you can give it a body and do that explicitly. For instance, you could make X mutable:
|
||||
|
||||
``` c#
|
||||
class Point(int X, int Y)
|
||||
|
|
|
@ -112,7 +112,7 @@ We should allow expression variables in queries, but keep them scoped to the ind
|
|||
|
||||
[csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287)
|
||||
|
||||
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
|
||||
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
|
||||
|
||||
There are solutions to that for API owners:
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
The way `base.` works in classes, if the base implementation that was present at compile
|
||||
time is removed at rune time, the CLR will search for the next implementation in the
|
||||
heirarchy and use that instead. For example,
|
||||
hierarchy and use that instead. For example,
|
||||
|
||||
```C#
|
||||
class A
|
||||
|
|
|
@ -25,7 +25,7 @@ This is the next major release (as C# 8.0 is a done deal at this point). Putting
|
|||
|
||||
## #146
|
||||
|
||||
There's something to this, but instead of marking seperately, we think it is paired with allowing nullary constructors on structs . For those we would warn on uses of `default(S)` that we can detect, similar to nullability.
|
||||
There's something to this, but instead of marking separately, we think it is paired with allowing nullary constructors on structs . For those we would warn on uses of `default(S)` that we can detect, similar to nullability.
|
||||
|
||||
## #812
|
||||
|
||||
|
|
128
meetings/2020/LDM-2020-01-06.md
Normal file
128
meetings/2020/LDM-2020-01-06.md
Normal file
|
@ -0,0 +1,128 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 6, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Use attribute info inside method bodies
|
||||
1. Making Task-like types covariant for nullability
|
||||
1. Casting to non-nullable reference type
|
||||
1. Triage
|
||||
|
||||
## Discussion
|
||||
|
||||
### Use attribute info inside method bodies
|
||||
|
||||
Examples:
|
||||
|
||||
1.
|
||||
|
||||
```C#
|
||||
bool TryGetValue<T>([MaybeNullWhen(false)]out T t)
|
||||
{
|
||||
return other.TryGetValue(out t); // currently warns
|
||||
}
|
||||
```
|
||||
|
||||
2.
|
||||
|
||||
```C#
|
||||
[return: MaybeNull]
|
||||
T GetFirstOrDefault<T>()
|
||||
{
|
||||
return null; // currently warns
|
||||
}
|
||||
```
|
||||
|
||||
3.
|
||||
|
||||
Overriding/implementation
|
||||
|
||||
```C#
|
||||
class A<T>
|
||||
{
|
||||
[return: MaybeNull]
|
||||
virtual T M();
|
||||
}
|
||||
|
||||
class B : A<string>
|
||||
{
|
||||
override string? M(); // warns about no [MaybeNull]
|
||||
}
|
||||
```
|
||||
|
||||
We don't have a complete design here, but some cases have an intuition about the correct
|
||||
behavior. In overriding, specifically, we need a specification for what it means for an
|
||||
annotation to be "compatible" with each of the attributes. On the other hand, it's not clear what
|
||||
the behavior of `MaybeNullWhenTrue` should be in all cases.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'd like to do this if the return on investment seems worth it, but to fully evaluate
|
||||
we need a proposal of the work.
|
||||
|
||||
### Making Task-like objects nullable covariant
|
||||
|
||||
This is a pretty common pain-point, and it's not the first time we special-cased variance,
|
||||
specifically `IEquatable<T>` is treated as nullable contravariant. It's unfortunate that the CLR
|
||||
doesn't have capability to make `Task<T>` full covariant, but handling even nullable alone would
|
||||
be useful. Moreover, if we later get the capability to mark `Task<T>` covariant, this would not
|
||||
harm the effort.
|
||||
|
||||
We also think that there may be some special cases introduced for overload resolution where we
|
||||
consider `Task<T>` as covariant already. If we could reuse that knowledge, that would be useful.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's see if we can dig up the overload resolution changes for Task-like types and try to adapt
|
||||
the same rule for making Task-like types nullable covariant.
|
||||
|
||||
### Casting to non-nullable reference type
|
||||
|
||||
Example:
|
||||
|
||||
```C#
|
||||
BoundNode? node = ...;
|
||||
if (node.Kind == BoundKind.Expression)
|
||||
{
|
||||
var x = (BoundExpression)node; // warning if node is nullable
|
||||
}
|
||||
```
|
||||
|
||||
The question is if this warning is valuable, or annoying. We've hit this most often in Roslyn
|
||||
when using the pattern `(object)x == null` to do a null check while avoiding the user-defined
|
||||
equality check. This is annoying in the Roslyn codebase, but not very common outside it. On the
|
||||
other hand, there's feeling that when doing the BoundNode to BoundExpression check, which is less
|
||||
common in Roslyn but more common generally, there's agreement that the warning is useful in
|
||||
making the type annotated with the most accurate representation of the null state.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Keep the warning, no change. We think the warning is valuable for the non-null-check cases. Newer
|
||||
version of C# have features that address the null check problem and Roslyn should move to use `x
|
||||
is null` or similar.
|
||||
|
||||
## Triage
|
||||
|
||||
Three related proposals: #3037, #3038, #377.
|
||||
|
||||
These all deal with the general problem of statements in expressions, especially statements in
|
||||
switch expressions, and switch expression form in statements.
|
||||
|
||||
They don't necessarily require each other, but they fit a lot of the same syntax and semantic
|
||||
space, so we should consider them all together.
|
||||
|
||||
There's also a sketch for how we could unify the syntax forms of all of three proposals, with
|
||||
potential syntax changes.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We agree that all of these proposals are addressing important scenarios, and some improvement
|
||||
here is valuable. We're not sure where we want to go with generalized statements-in-expressions
|
||||
vs. adding special syntax forms for switch expression/statement.
|
||||
|
||||
We're mainly concerned that if we do switch expression blocks, we want to make sure that the we
|
||||
don't block a future generalization to all expressions. We need to find a generalization that we
|
||||
like, reject a generalization and accept this syntax, or put these improvements on the
|
||||
back-burner if we think that a generalization is possible, we just haven't found it.
|
||||
|
||||
Accepted for C# 9.0 investigation.
|
188
meetings/2020/LDM-2020-01-08.md
Normal file
188
meetings/2020/LDM-2020-01-08.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 8, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Discussion
|
||||
|
||||
### Unconstrained type parameter annotation T??
|
||||
|
||||
You can only use `T?` when is constrained to be a reference type or a value type. On the other
|
||||
hand, you can only use `T??` when `T` is unconstrained.
|
||||
|
||||
The question is what to use for the following:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
```
|
||||
|
||||
Our understanding is that this would be:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// We think the correct annotation is
|
||||
// void F<U>(ref U? u) where U : class
|
||||
// because the type parameter is no longer unconstrained. The `where U : class`
|
||||
// constraint is required, as U? would otherwise mean U : struct
|
||||
}
|
||||
// We may want to allow U?? even in the above case, so
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // allowed?
|
||||
// This would not require the `where U : class` constraint because `U??` cannot
|
||||
// be confused with `Nullable<U>`
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
```
|
||||
|
||||
In the above, we're wondering whether we should allow `??` without warning even in cases where
|
||||
there's existing syntax to represent the semantics. One benefit is that you could write
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override F<U>(ref U?? u) { }
|
||||
}
|
||||
```
|
||||
|
||||
Since the override cannot be confused with `U?`, there's no need for the `where U : class`. On the
|
||||
other hand, the benefit seems marginal and it's not needed to represent the semantics. It could
|
||||
also be added later.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the syntax `??` to represent the "maybe default" semantics. We think that `??` should be
|
||||
allowed in the cases where we have other syntax to represent the semantics. A warning will be
|
||||
provided and hopefully a code fix to move the code to the "more canonical" form. The syntax `??`
|
||||
is only legal on type parameters in a nullable-enabled context.
|
||||
|
||||
We considered using the `?` syntax the represent the same semantics, but ruled it out for a couple reasons:
|
||||
|
||||
1. It's technically difficult to achieve. There are two technical limitations in the compiler.
|
||||
The first is design history where a type parameter ending in `?` is assumed to be a struct. This
|
||||
has been true in the compiler all the way until nullable reference types in the last release. The
|
||||
second problem is that many pieces of C# binding are very sensitive to being asked if a type is a
|
||||
value or reference type and asking the question can often lead to cycles if asked before the answer
|
||||
is absolutely necessary. However, `T?` means different things if `T` is a struct or unconstrained, so
|
||||
finding the safe place to ask is difficult.
|
||||
|
||||
2. Unresolved design problems. In overriding `T?` means `where T : struct`, going back to the beginning of
|
||||
`Nullable<T>`. This already caused problems with `T? where T : class` in C# 8, which is why we introduced
|
||||
a feature where you could specify `T? where T : class` in an override, contrary to past C# design where
|
||||
constraints are not allowed to be re-specified in overrides. To accommodate overloads for unconstrained
|
||||
`T?` we would have to introduce a new type of explicit constraint meaning *unconstrained*. We don't have
|
||||
a syntax for that, and don't particularly want one.
|
||||
|
||||
3. Confusion with a different feature. If we use the `T?` syntax, the following would be legal:
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public T? M<T>() where T : notnull { ... }
|
||||
}
|
||||
```
|
||||
|
||||
what you may think is that `M` returns a nullable reference type if `T` is a reference type, and a nullable
|
||||
value type if `T` is a value type (i.e., `Nullable<T>`). However, that's *not* what this feature is. This
|
||||
feature is essentially *maybe default*, meaning that `T?` may contain the value `default(T)`. For a reference
|
||||
type this would be `null`, but for a value type this would be the zero value, not `null`.
|
||||
|
||||
Moreover, this seems like a useful feature, that would be ruled out if we used the syntax for something else.
|
||||
Consider a method similar to `FirstOrDefault`, `FirstOrNull`:
|
||||
|
||||
```C#
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> e) where T : notnull
|
||||
```
|
||||
|
||||
The benefit is that there is a single sentinel value, `null`, and that the full set of values in a struct
|
||||
could be represented. In `FirstOrDefault`, if the input is `IEnumerable<int>` there is no way to distinguish
|
||||
a non-empty enumerable with first value of `0`, or an empty enumerable. With `FirstOrNull` you can distinguish
|
||||
these cases in a single call, as long as `null` is not a valid value in the type.
|
||||
|
||||
Due to CLR restrictions it is not possible to implement this feature in the obvious way, so this feature may
|
||||
never be implemented, but we would like to prevent confusion and keep the syntax available in case we ever
|
||||
figure out how we'd like to implement it.
|
||||
|
||||
### Covariant returns
|
||||
|
||||
We looked at the proposal in #2844.
|
||||
|
||||
There's a proposal that for the following
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
`I2.M` would be illegal as it is, because this is an interface *implementation*, not an
|
||||
*override*. Interface implementation would not change return types, while overriding would. Thus
|
||||
we would allow
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
override string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
which would allow the return type to change. However, it would override *all* `M`s, since
|
||||
explicit implementation is not allowed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like it in principle and would like to move forward. There may be some details around
|
||||
interface implementation vs. overriding to work out.
|
159
meetings/2020/LDM-2020-01-15.md
Normal file
159
meetings/2020/LDM-2020-01-15.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 15th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Working with data
|
||||
2. Record feature breakdown
|
||||
|
||||
## Discussion
|
||||
|
||||
### Working with data
|
||||
|
||||
As we discuss records, we want to go over a design document we produced a number of years ago
|
||||
called "working with data." This document lays out how, when we design features, we inherently
|
||||
express a "path of least resistance," which consists of the features that seem easiest or
|
||||
shortest to use to accomplish a given problem.
|
||||
|
||||
Link: https://github.com/dotnet/csharplang/issues/3107
|
||||
|
||||
The document argues that, as we find particular patterns to be more effective at building
|
||||
software, we should make the forms we find to be more effective simpler or shorter to express in
|
||||
the language. We should not "change" our opinions, meaning make old syntax illegal, but we should
|
||||
"level the playing field" by making other forms simpler.
|
||||
|
||||
The conclusion of the design document is that we should favor
|
||||
|
||||
1. Immutable members in records by default
|
||||
1. Any features from records that we separate should not make the simple syntax longer
|
||||
|
||||
### Record feature breakdown
|
||||
|
||||
We've also been working on breaking down the individual features
|
||||
of records and determining how independent they can or should be.
|
||||
|
||||
Notes: https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
There seem to be the following somewhat separable parts of records
|
||||
|
||||
1. Value-based equality
|
||||
2. Construction boilerplate
|
||||
3. Object initializers
|
||||
4. Nondestructive mutation
|
||||
5. Data-friendly defaults
|
||||
|
||||
#### Value equality
|
||||
|
||||
It's been proposed that a `key` modifier could be applied to signal that value-based equality is
|
||||
being generated based on the members which have it. This works in many cases,
|
||||
but if the absence of the `key` modifier means inherited equality, we're not sure
|
||||
that's the semantics we want. It would also not allow value-based equality to be
|
||||
"specified" in the base class in some sense, enforcing value equality for the deriving
|
||||
type. Whether this is valuable or blocking is an open question.
|
||||
|
||||
#### Construction boilerplate
|
||||
|
||||
Creating a constructor to assign members of a container is one of the largest sources
|
||||
of repetitive boilerplate, e.g.
|
||||
|
||||
```C#
|
||||
public class Point
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public Point(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can imagine various points on this spectrum to simplify the boilerplate,
|
||||
|
||||
```C#
|
||||
public class Point
|
||||
{
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
public Point(X, Y); // name matching and type absence implies initialization
|
||||
}
|
||||
```
|
||||
|
||||
Which removes the duplication of naming the same elements multiple times or,
|
||||
|
||||
```C#
|
||||
public class Point(X, Y)
|
||||
{
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
}
|
||||
```
|
||||
|
||||
Which removes the constructor name duplication and we could go further to remove
|
||||
property name duplication,
|
||||
|
||||
```C#
|
||||
public class Point(
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
);
|
||||
```
|
||||
|
||||
Going all the way to the original position deconstruction
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y);
|
||||
```
|
||||
|
||||
Where we pick a point in this space seems to correspond to the perceived benefits
|
||||
of the orthogonality of the feature. If the construction shorthand is useful for
|
||||
many scenarios outside of the record scenarios, it's practical to expand it.
|
||||
|
||||
### Object initializers
|
||||
|
||||
One benefit to object initializers is that they don't refer to a constructor directly,
|
||||
only to the properties. This sidesteps a weakness in C#, where constructor initialization in inheritance requires repetition. Without constructors the simple
|
||||
relation
|
||||
|
||||
```C#
|
||||
public abstract class Person
|
||||
{
|
||||
public string Name { get; }
|
||||
}
|
||||
public class Student : Person
|
||||
{
|
||||
public string ID { get;}
|
||||
}
|
||||
```
|
||||
|
||||
has no repetition. Each class states only the properties that are essential, and
|
||||
for derived classes all the base properties are inherited without repetition.
|
||||
Once you add constructors this breaks down
|
||||
|
||||
```C#
|
||||
public abstract class Person
|
||||
{
|
||||
public string Name { get; }
|
||||
public Person(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
public class Student : Person
|
||||
{
|
||||
public string ID { get; }
|
||||
public Student(string id, string name)
|
||||
: base(name)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the derived classes have to repeat everything from the base, causing brittleness
|
||||
along the boundary. If we were to imagine some improvement to object initializers,
|
||||
then defining a constructor would not be required.
|
||||
|
||||
On the other hand, this also removes one of the main benefits for having a constructor,
|
||||
namely that you can validate the whole state of the object before producing it.
|
108
meetings/2020/LDM-2020-01-22.md
Normal file
108
meetings/2020/LDM-2020-01-22.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
|
||||
# C# Language Design Notes for Jan. 22, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Top-level statements and functions
|
||||
2. Expression Blocks
|
||||
|
||||
## Discussion
|
||||
|
||||
### Top-level statements and functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
Three main scenarios:
|
||||
|
||||
1. Simple programs are simple -- remove the boilerplate for Main
|
||||
|
||||
2. Top-level functions. Members outside of a class.
|
||||
|
||||
3. Scripting/interactive. Submission system allows state preservation across evaluations.
|
||||
|
||||
Unfortunately, some of these proposals interact in difficult ways.
|
||||
|
||||
If you write
|
||||
|
||||
```C#
|
||||
int x = ...;
|
||||
```
|
||||
|
||||
is `x` now a global mutable variable for the entire program? Or is it a
|
||||
local variable in a generated Main method?
|
||||
|
||||
#### Proposal: Simple programs
|
||||
|
||||
The proposal is to prioritize (1) and (3) and remove boilerplate, while
|
||||
enabling use in scripting/interactive scenarios.
|
||||
|
||||
To address (1), we would allow a single file in the compilation to contain top-level statements,
|
||||
and any file to contain top-level local functions, which would be in scope in all files, but it
|
||||
would be an error to refer to them.
|
||||
|
||||
There's wide consensus that (1) is very useful. There's the case of small programs, where you
|
||||
really just want to write a few statements and not have to write the boilerplate of classes and
|
||||
Main. It's also a very large learning burden in that just to write "Hello, World" requires
|
||||
explaining methods, classes, static, etc.
|
||||
|
||||
(3) is also important partly because there are a number of products and scenarios
|
||||
currently using the scripting system. We should keep that in mind to make sure that
|
||||
we don't prevent a large number of use cases from ever using the new system.
|
||||
|
||||
We think (2) is interesting and worth considering. It may not be the highest priority,
|
||||
but we need to make sure we don't rule it out entirely. We also think that if we add
|
||||
(1) it seems likely that some people would want (2) much sooner.
|
||||
|
||||
If we do want to make space for (2) we should make sure to look at lookup rules very carefully.
|
||||
The C# lookup rules are very complicated and including new ones for top-level members could
|
||||
include subtle ways that change new code.
|
||||
|
||||
When we designed scripting we had experience that copying back-and-forth from interactive and the
|
||||
main program is very useful and important. Because the syntax used here is similar to local
|
||||
functions and it's not currently proposed that accessibility modifiers are legal, this would
|
||||
create a difference when copying code between standard C# and the interactive dialect, since
|
||||
presumably those declarations would now be illegal.
|
||||
|
||||
#### Block expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
We're revisiting the earlier discussions and there is a proposal for how we could
|
||||
make blocks legal as expressions. The proposed precedence would be the lowest
|
||||
possible, so many ambiguities or breaking changes would be avoided.
|
||||
|
||||
Examples:
|
||||
|
||||
```C#
|
||||
var x = { ; 3 }; // int x = 3
|
||||
var x = { {} 3 }; // int x = 3
|
||||
```
|
||||
|
||||
Note that a final expression is required, so the `'` or `{}` are necessary as "starting
|
||||
statements".
|
||||
|
||||
The most notable restrictions are that you cannot branch out, meaning that `return`, `yield break`, and `yield return`, and `goto` would be illegal in this proposal.
|
||||
|
||||
Something which was brought up before is whether to use a "trailing expression" to
|
||||
produce a value, or introduce some sort of statement to produce the evaluation
|
||||
expression. If we used a `break e;` syntax, the above could look like
|
||||
|
||||
```C#
|
||||
var x = { break 3; };
|
||||
```
|
||||
|
||||
One problem is turning the block into a lambda, where `break` would have to be changed to
|
||||
`return`. On the other hand, if this code were introduced directly into the method, `return`
|
||||
would actually produce different, valid semantics. `break e;` would be an error in both contexts,
|
||||
instead of producing different code.
|
||||
|
||||
The precedence doesn't have agreement. Some people think that the precedence is
|
||||
still too high and that we should almost always require a parenthesized wrapper
|
||||
expression, except in specific cases where we think it's clear. Other people think
|
||||
that this is too low and they want to use them in more places.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We don't think we have enough information about the restrictions we're working under. One way to
|
||||
make progress would be to construct a list of the potential ambiguities in using the `{}` as an
|
||||
expression term.
|
106
meetings/2020/LDM-2020-01-29.md
Normal file
106
meetings/2020/LDM-2020-01-29.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
|
||||
# C# Language Design for Jan. 29, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Record -- With'ers
|
||||
|
||||
## Discussion
|
||||
|
||||
In this meeting we'd like to talk about Mads's write-up of the "With-ers" feature, as it relates
|
||||
to records. Multiple variations have been proposed, but the suggestion generally takes the form
|
||||
of a `with` expression that can return a copy of a data type, with selective elements changed.
|
||||
|
||||
Write-up: https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
The first thing we learned is that the fundamental problem we're trying to solve is
|
||||
"non-destructive mutation."
|
||||
|
||||
There are two approaches we've thought of: direct copy and then direct modification, and creation of a new type based on the values of the old type.
|
||||
|
||||
1. Direct copy. We might call this "copy-and-update" because we copy the new data type exactly,
|
||||
then update the new type with required changes. The basic implementation would be to use
|
||||
MemberwiseClone, and then overwrite select properties.
|
||||
|
||||
2. Create a new type. we call this "constructing through virtual factories." If the type supports
|
||||
a constructor, this approach would call the constructor using the new values, or the existing
|
||||
ones if nothing new is given. The construction would be virtual so that derived types would not
|
||||
lose state when called through the base type.
|
||||
|
||||
There are advantages and disadvantages to each proposal.
|
||||
|
||||
(1) is simple but seemingly dangerous. There are often internal constraints to a type which must
|
||||
be preserved for correctness. Usually this is enforced through the type constructor and
|
||||
visibility of modification. That would not necessarily be available here.
|
||||
|
||||
(2) does construction similar to conventional construction today, so it doesn't introduce as many
|
||||
safety concerns. On the other hand, the contract looks a lot more complicated. To make the
|
||||
feature seem simple on the surface, it looks like we imply a lot of implicit dependency. For
|
||||
example,
|
||||
|
||||
```C#
|
||||
public data class Point(int X, int Y);
|
||||
var p2 = p1 with { Y = 2 };
|
||||
```
|
||||
|
||||
Would generate
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(int X, int Y) => new Point(X, Y);
|
||||
}
|
||||
var p2 = p1.With(p1.X, 2);
|
||||
```
|
||||
|
||||
The first requirement is that an auto-generated `With` method must have a primary constructor, in
|
||||
order to know which constructor to call. Alternatively, we could have a `With` method generated
|
||||
for every constructor, although that would require a syntax to signal that `With` methods should
|
||||
be generated in the absence of a primary constructor.
|
||||
|
||||
The compiler also needs to know that the `X` and `Y` parameters of the `With` method correspond
|
||||
to particular properties, so it can fill in the defaults in the `with` expression. Otherwise we
|
||||
would need some way of signifying which of the parameters are meant to be "defaults":
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(bool[] provided, int X, int Y)
|
||||
{
|
||||
return new Point(provided[0] ? X : this.X, provided[1] ? Y : this.Y);
|
||||
}
|
||||
}
|
||||
var p2 = p1.With(new bool { false, true}, default, 2);
|
||||
```
|
||||
|
||||
We also need to figure out which `With` method to call at a particular call site. One way is to
|
||||
construct an equivalent call and perform overload resolution. Another way would be to pick a
|
||||
particular `With` method as primary, and always use that one in overload resolution.
|
||||
|
||||
This also has some of the same compatibility challenges that we've seen in other areas.
|
||||
Particularly, if you add members to the record, there will be a new `With` method with a new
|
||||
signature. This would break existing binaries referencing the old `With` method. In addition, if
|
||||
you add a new `With` method, the old one would still be chosen by overload resolution, if
|
||||
overload resolution is performed, as long as unspecified properties in the `with` expression are
|
||||
default values.
|
||||
|
||||
On the other hand, this is also a general problem with backwards compatibility overloads. We'll
|
||||
need to investigate whether we want to add a general purpose mechanism for handling backwards
|
||||
compatibility and if we want to introduce a special case for With-ers specifically.
|
||||
|
||||
What all of the above interdependency implies is that we need a significant amount of syntax or
|
||||
"hints" about what to do during autogeneration. We previously expressed interest in providing
|
||||
orthogonality for as many of the "record" features as possible. A conclusion is that
|
||||
auto-generated With-ers require or suggest many of syntactic and semantic components of records
|
||||
themselves. When we try to separate the feature entirely, we require user opt-in to specify the
|
||||
"backing" state of the With-er. This seems to imply that auto-generation should
|
||||
not be a general, orthogonal feature, but a specific property of records.
|
||||
|
||||
However, we don't have to give up orthogonality entirely. The requirements for auto-generated
|
||||
With-ers doesn't imply anything about manually written With-ers. Auto-generation seems possible
|
||||
in records because the syntax ties the state to the public interface. Manual specification looks
|
||||
just like the components of records that can be written explicitly in regular classes, like
|
||||
constructors themselves. If we do want to pursue this avenue, we should try to limit
|
||||
the complexity of the pattern as much as possible. It's not too bad if it's fully
|
||||
generated by the compiler, but it can't be very complicated if we want users to write
|
||||
it themselves.
|
61
meetings/2020/LDM-2020-02-03.md
Normal file
61
meetings/2020/LDM-2020-02-03.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
|
||||
# C# LDM for Feb. 3, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Value equality
|
||||
|
||||
## Discussion
|
||||
|
||||
We split our discussion between two proposals, which end up being very
|
||||
similar.
|
||||
|
||||
### 'key' equality proposal
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3127
|
||||
|
||||
Q: Is comparing `System.Type`s slow? Does it require reflection?
|
||||
|
||||
A: `typeof` does not require reflection and comparing `Type`s is fast.
|
||||
|
||||
Q: Why use a KeyEquals method?
|
||||
|
||||
A: To signify that value equals is used and delegate to the base equality,
|
||||
when the base opts-in to value equality. By having a well-known signature
|
||||
in metadata, no special attributes are required for derived types to discover
|
||||
the pattern.
|
||||
|
||||
Q: Is KeyEquals necessary? Can we use `EqualityContractOrigin` to figure
|
||||
out that the type implements value equality?
|
||||
|
||||
A: Yes, that seems like it should work.
|
||||
|
||||
*Discussion*
|
||||
|
||||
There's some concern that modifying the public surface area of the type
|
||||
itself without any type of modifier is too much magic. If we have some
|
||||
sort of modifier that goes on the type, in addition to the "key" members,
|
||||
it would be clear that the type implements value equality from the type
|
||||
declaration, in addition to the member declarations.
|
||||
|
||||
This dovetails into records as a whole in that it would allow the feature sets to be separable.
|
||||
If a type could have value equality or be a record, the features could be combined to produce a
|
||||
value record, or the value equality could be left off to allow a record with reference equality.
|
||||
There's some disagreement on whether this is a positive or a negative. If you view a record as
|
||||
appropriately having value equality, this is a negative, or vice versa.
|
||||
|
||||
### 'value' equality proposal
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
The most visible difference here is that `value` is the name of the modifier, instead of `key`.
|
||||
This more accurately reflects the term "value equality", but it's unfortunate that we already
|
||||
have the term "value type" which has a completely different meaning in the language.
|
||||
|
||||
At the moment the proposal also doesn't include the "extra" members, like a strongly
|
||||
typed Equals, the `==`/`!=` operators, and `IEquatable` interface implementation.
|
||||
|
||||
There's an open question as to whether this feature is preferred for a discriminated
|
||||
union scenario or not. We have two examples in Roslyn of discriminated unions, our
|
||||
symbol tree and our bound tree, and they have almost completely different equality
|
||||
contracts.
|
133
meetings/2020/LDM-2020-02-05.md
Normal file
133
meetings/2020/LDM-2020-02-05.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
|
||||
# C# LDM for Feb. 5, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Dependent nullability attribute
|
||||
2. Null checking in unconstrained generics
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nullability
|
||||
|
||||
Dependent calls:
|
||||
|
||||
We'd like to be able to support patterns like:
|
||||
|
||||
```C#
|
||||
if (x.HasValue)
|
||||
{
|
||||
x.Value // should not warn when HasValue is true
|
||||
}
|
||||
```
|
||||
|
||||
We would add attributes to support these use cases: `EnsuresNotNull` and `EnsuresNotNullWhen` (to parallel the existing `NotNull` attributes). The
|
||||
proposal as stands is to name the fields or properties and that would be
|
||||
the inputs and outputs for the flow analysis. We propose that the lookup
|
||||
rules for resolving the names in these instances would be similar to the
|
||||
rules for the `DefaultMemberAttribute`.
|
||||
|
||||
This could also be used for helpers in constructor initialization, where today constructors which
|
||||
only call `base` are required to initialize all members, even if a helper initializes some of the
|
||||
members.
|
||||
|
||||
There's a follow-up question: should you be able to specify that members of the parameter are
|
||||
not-null after the call? For example,
|
||||
|
||||
```C#
|
||||
class Point
|
||||
{
|
||||
public object? X;
|
||||
}
|
||||
static void M([EnsuresNotNull(nameof(Point.X))]Point p) { ... }
|
||||
```
|
||||
|
||||
We could also allow it for nested annotation
|
||||
|
||||
```C#
|
||||
class Point
|
||||
{
|
||||
public object? X;
|
||||
}
|
||||
class Line
|
||||
{
|
||||
public Point P1;
|
||||
public Point P2;
|
||||
}
|
||||
static void M([EnsuresNotNull("P1.X", "P1.Y")]Line l) { ... }
|
||||
static bool M([EnsuresNotNullWhen(true, "P1.X", "P1.Y")]Line l) { ... }
|
||||
```
|
||||
|
||||
The nested names could also be used for return annotations.
|
||||
|
||||
However, we're not sure this is worth it. We see the usefulness in theory,
|
||||
but we're not sure how often it would actually be used. If we want to leave
|
||||
the space for later, we could produce an error when writing an attribute with
|
||||
an unsupported string form.
|
||||
|
||||
Similarly, if we don't want to support referring to members of types through
|
||||
the parameters, as in the first example, we can also provide an error for these
|
||||
scenarios. Or, we could say that the initial proposal is qualitatively different
|
||||
from all these scenarios:
|
||||
|
||||
```C#
|
||||
[MemberNotNull(nameof(X))]
|
||||
void M() { }
|
||||
```
|
||||
|
||||
For the situations in parameters and return types we are referring to the target the attribute is
|
||||
being applied to, while the original proposal is returning to the containing type, somewhat
|
||||
unrelated to the location of the attribute.
|
||||
|
||||
We're also not sure exactly what the name or shape of these attributes would
|
||||
look like. We think this could be valuable, but we'd like to decide with
|
||||
the full context of other attributes we're considering.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We see the usefulness of the original scenario, but the broadening we're not sure
|
||||
on the return on investment. Let's support the original scenario through a new
|
||||
attribute, `MemberNotNull`, that only has members as valid attribute targets to start.
|
||||
|
||||
If we find users still hit significant limitations without the parameter and return
|
||||
type support, we can consider broadening in a future release.
|
||||
|
||||
### Pure non-null check in generic
|
||||
|
||||
```C#
|
||||
public T Id<T>(T t)
|
||||
{
|
||||
if (t is null) Console.WriteLine();
|
||||
return t; // warn?
|
||||
}
|
||||
```
|
||||
|
||||
The question here is whether we should consider `T` to move to `MaybeDefault` after
|
||||
checking for `null`. We never do this today.
|
||||
|
||||
The scenario where a "pure" null check would come into play is:
|
||||
|
||||
```C#
|
||||
public T Id<T>(T t) where T : notnull
|
||||
{
|
||||
if (t is null) Console.WriteLine();
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
It appears that this does not warn today, which looks like a bug. The analogous
|
||||
scenario for `T??` is
|
||||
|
||||
```C#
|
||||
public T ID<T>(T t)
|
||||
{
|
||||
if (t is default) Console.WriteLine();
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
However, this is illegal as there is no way to check if `T` is `default(T)`.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The original scenario should not warn.
|
48
meetings/2020/LDM-2020-02-10.md
Normal file
48
meetings/2020/LDM-2020-02-10.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
# C# Language Design for Feb. 10, 2020
|
||||
|
||||
# Agenda
|
||||
|
||||
Records
|
||||
|
||||
# Discussion
|
||||
|
||||
We're continuing our attempt to draw out the dependencies and individual features inside records.
|
||||
|
||||
When going through the list, what stands out is:
|
||||
|
||||
- Looking at `with`, we need to figure out what's mentionable in the `with` expression.
|
||||
|
||||
- We need to figure out exactly what we want for how primary constructors fit into records
|
||||
|
||||
There are a number of positives and negatives of primary constructors. On the negative side,
|
||||
a non-record primary constructor seems to consume syntactic space that could be used for
|
||||
records. If we think that records are the overwhelmingly common scenario, then it seems like
|
||||
using the shortest syntax for the most common feature is useful. On the positive side, primary
|
||||
constructors alone seem to support a simpler way of writing private implementation details.
|
||||
Separate from the value as a whole, there's some desire to have a special keyword just for
|
||||
records. That is, even if we didn't do primary constructors, it could be valuable to have
|
||||
an explicit modifier, like `data` to signify that this type has special behavior.
|
||||
|
||||
One possible pivot is to eliminate some composition syntax entirely, by creating a new type
|
||||
of declaration, `record`, e.g.
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y);
|
||||
```
|
||||
|
||||
This would be equivalent to the syntax that we've been discussing with `data`, namely
|
||||
|
||||
```C#
|
||||
data class Point(int X, int Y);
|
||||
```
|
||||
|
||||
but since the `class` keyword is implied by default, the most common scenario would be
|
||||
just about as short as the shorter `class Point(int X, int Y)` form.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
After taking everything into account, we think having an new keyword for records is good both for
|
||||
leaving space for non-record primary constructors, and also to serve as a clear signifier of
|
||||
record semantics.
|
||||
|
116
meetings/2020/LDM-2020-02-12.md
Normal file
116
meetings/2020/LDM-2020-02-12.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
|
||||
# C# Language Design for Feb 12, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Value equality
|
||||
|
||||
Proposal: use the `key` keyword previously mentioned, but also
|
||||
require it on the type declaration as well, e.g.
|
||||
|
||||
```C#
|
||||
key class HasValueEquality
|
||||
{
|
||||
public key int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
There are a number of things we could pivot on
|
||||
|
||||
```C#
|
||||
key class HasValueEquality1 { public key int X { get; } }
|
||||
class HasValueEquality2 { public key int X { get; } }
|
||||
key class HasValueEquality3 { public key X { get; } }
|
||||
class HasValueEquality4 : IEquatable<HasValueEquality4> { public int X { get; } }
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
```C#
|
||||
record Point1(int X); // Implies value equality over X
|
||||
record Point2a(int X); // Implies inherited equality
|
||||
key record Point2b1(int X); // Implies value equality over X
|
||||
key record Point2b2a(int X); // Implies "empty" value equality
|
||||
key record Point2b2b(key int X); // Implies value equality over X
|
||||
|
||||
|
||||
key class Point3a(int X); // implies record + value equality over X
|
||||
data class Point3b(int X); // implies record with inherited equality
|
||||
```
|
||||
|
||||
#### Equality default
|
||||
|
||||
We originally considered adding value equality on records both because it's difficult to
|
||||
implement yourself and it fits the semantics we built for records in general. We want to validate
|
||||
that these things are still true, and new considerations, namely whether it is the appropriate
|
||||
default for records and whether it should be available to other types, like regular classes.
|
||||
|
||||
We left off in the previous discussion asking whether value equality is not just
|
||||
an inconvenient default, but actively harmful for key scenarios for records. Some examples
|
||||
we came up with are either classes with large numbers of members, where value equality may
|
||||
be unnecessary and slow, and circular graphs, where using value equality could cause
|
||||
infinite recursion.
|
||||
|
||||
These do seem bad, but it's not obvious that these scenarios either fit perfectly with the
|
||||
canonical record, or if the consequences are necessarily worse than default reference equality.
|
||||
Certainly producing infinite recursion in object graphs is bad, but silently incorrect behavior
|
||||
due to inaccurate reference equality is also harmful, in the same sense. It's also easier
|
||||
to switch from value equality to reference equality than it is to switch from reference equality
|
||||
to value equality, due to the complex requirements in a value equality contract.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Value equality seems a reasonable default, as long as they are immutable by default, and that
|
||||
there is a reasonable way to opt-in to a different equality.
|
||||
|
||||
#### Separable value equality
|
||||
|
||||
Given that we like value equality as a default, we have to decide if we want a separable equality
|
||||
feature as well. This is important for the scenario:
|
||||
|
||||
```C#
|
||||
record Point1(int X)
|
||||
{
|
||||
public int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
if there's a separate `key` feature, we need to decide if the substituted property should
|
||||
require, allow, or disallow the `key` modifier, e.g.
|
||||
|
||||
```C#
|
||||
record Point1(int X)
|
||||
{
|
||||
public key int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
We also need to decide what such a "separable" equality feature would look like, and if it has a
|
||||
difference between records and other classes. We could add a `key` feature for non-records, and
|
||||
disallow `key` entirely in records. The members of a record equality would then not be
|
||||
customizable.
|
||||
|
||||
The individual `key` modifiers on non-records seem deceptively complicated.
|
||||
|
||||
A common case is "opt-in everything". `key` modifiers wouldn't improve much on this, as they
|
||||
would be necessary on every element. On the other hand, there are often computed properties that
|
||||
may be seen as part of "everything", but not part of the equality inputs. The plus of record
|
||||
primary constructors is that they identify the "core" inputs to the type.
|
||||
|
||||
Individual `key` modifiers also do not help with the large custom classes that are written today
|
||||
where it's easy to forget to add new members to equality. With a `key` modifier you can still
|
||||
forget to add the modifier to a new member.
|
||||
|
||||
These decisions play into records as a whole because they affect the uniformity of record and
|
||||
non-record behavior. If records are defined by their "parameters", namely in this syntax the
|
||||
primary constructor parameters and identically named properties, then no other members should
|
||||
be a part of the equality. However, that would imply members in the body are not automatically
|
||||
included. For regular classes, it seems backwards. Members are not generally included, they have
|
||||
to be added specifically.
|
||||
|
||||
On the other hand, if we prioritize uniformity, general members in record bodies would be included
|
||||
in equality, which would harm a view of records as consisting primarily of the "record inputs."
|
82
meetings/2020/LDM-2020-02-19.md
Normal file
82
meetings/2020/LDM-2020-02-19.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
|
||||
# C# Language Design for Feb. 19, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
State-based Value Equality
|
||||
|
||||
## Discussion
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/3213
|
||||
|
||||
* We haven't decided (yet) to add support for value equality on all
|
||||
classes (separate from records)
|
||||
|
||||
* The behavior is actually that all fields _declared_ in the class are
|
||||
members in the value equality, not all fields in the class (since inherited fields are not
|
||||
included)
|
||||
|
||||
* Inheritance would be implemented using the previously described
|
||||
proposals using the `EqualityContract` property
|
||||
|
||||
* Records wouldn't behave differently, except that they have `value` by
|
||||
default
|
||||
|
||||
* The main difference with how records work in other places is that the semantics
|
||||
of a record is otherwise decided by the members of the primary constructor, while in this
|
||||
proposal the members of the record primary constructor have no special contribution to the value
|
||||
equality semantics
|
||||
|
||||
* There's an evolution risk where we want to provide more complex things, like deep
|
||||
equality, but these features don't support enough complexity to add it. Instead, we end up just
|
||||
adding more keywords or more attributes. Consider array fields. The default equality is reference
|
||||
equality, but sequence equality isn't particularly rare. How would users customize that?
|
||||
A new keyword? Attribute? Writing Equals manually?
|
||||
|
||||
* Turns out we're finding a lot of customization pivots. String comparison is another one.
|
||||
If we want to support all these scenarios attributes could be better. If we could use
|
||||
attributes to supply an EqualityComparer that would be almost completely customizable.
|
||||
|
||||
* If equality is this complicated, should we only support simple generated equality for
|
||||
records? Can we leave more complicated scenarios to tooling, like source generators?
|
||||
|
||||
Record equality: use the "primary" members or use all fields?
|
||||
|
||||
* Using all the fields is consistent with how structs work
|
||||
|
||||
* Using the "primary" members mirrors how the generation of `With` or other things
|
||||
generated by a record with a primary constructor
|
||||
|
||||
* There does seem to be a possibility that after you get to a certain size, positional
|
||||
records are less useful. In that case we want a path to the nominal record. If we do want the
|
||||
nominal path, it's generally desirable that we want as little "record" syntax as possible.
|
||||
If we choose the struct "use all the fields" approach, then we could use exactly the
|
||||
same mechanism for both the "nominal" and the "positional" records.
|
||||
|
||||
* The nominal record syntax that has been floated is
|
||||
|
||||
```C#
|
||||
record Point { int X; int Y; }
|
||||
```
|
||||
|
||||
which generates
|
||||
|
||||
```C#
|
||||
record Point
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
Aside from the shorthand for properties, this generates Equals, GetHashCode and some form
|
||||
of "With", which doesn't seem much different from proposals for a separable value equality. Is
|
||||
there really much point in separating these proposals?
|
||||
|
||||
* One completely opposite possibility: bypass the question by prohibiting private members in the
|
||||
positional record entirely
|
||||
|
||||
**Conclusion**
|
||||
|
||||
No hard decisions yet. Leaning slightly towards using "all declared fields" as the metric for
|
||||
value equality. There's some support for the "no private fields approach."
|
41
meetings/2020/LDM-2020-02-24.md
Normal file
41
meetings/2020/LDM-2020-02-24.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
# C# Language Design for Feb. 24, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Nominal records proposal
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nominal records
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3226
|
||||
|
||||
We've been trying to leave space open for something we're calling "nominal records" where the
|
||||
concept is that we establish some new system for constructing types based on names, instead of
|
||||
the order of parameters in a constructor.
|
||||
|
||||
Here we have a refreshed nominal records proposal to examine and consider.
|
||||
|
||||
The proposal says:
|
||||
|
||||
> The main thing you lose out on with nominal construction is a centralized place - the
|
||||
constructor body - for validation. Property setters can have member-wise validation, but
|
||||
cross-member holistic validation is not possible. However, for a feature such as records that is
|
||||
for data not behaviors, that seems to be a particularly small sacrifice.
|
||||
|
||||
We don't necessarily agree that this is a small restriction, and there may be some way to add
|
||||
support for it.
|
||||
|
||||
When it comes to the `With` we do need to decide what members are copied over in non-destructive
|
||||
mutation. One strategy is to use the "surface area" of the object, which is defined as the
|
||||
constructor parameters, along with the public fields and properties that have some sort of
|
||||
"setter".
|
||||
|
||||
Alternatively, we could copy over the state of the object. This would be equivalent to the use
|
||||
of the `MemberwiseClone` approach as we discussed in previous design meetings.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
There are many details to work out, but there's consensus that we want to investigate adding
|
||||
nominal records in the future.
|
123
meetings/2020/LDM-2020-02-26.md
Normal file
123
meetings/2020/LDM-2020-02-26.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
|
||||
# C# Language Design Meeting for Feb. 26, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Design Review
|
||||
|
||||
## Discussion
|
||||
|
||||
Today is a design review, where we collect the design team, selected emeritus
|
||||
members, and a number of broad ecosystem experts to provide some "in-the-moment"
|
||||
feedback to our current designs and their directions.
|
||||
|
||||
Today we presented more of our thoughts on the top-level statements/"simple programs"
|
||||
features and records.
|
||||
|
||||
### Simple Programs
|
||||
|
||||
We have a prototype of simple programs. As per the existing spec, you can have
|
||||
top-level functions among all files, and other statements in a single file.
|
||||
|
||||
Collected feedback, not in any particular order:
|
||||
|
||||
* Supporting local functions in files other than the top-level statement file doesn't
|
||||
seem useful and could cause confusion. If there's a local function defined in a file
|
||||
only with classes, it would appear that that function would be in scope for the
|
||||
classes, according to C# lexical scoping conventions. However, since these are defined
|
||||
as *local functions*, not top-level methods, it would be an error to use them inside
|
||||
the class. Moreover, even if that confusion is resolved, there doesn't seem to be a
|
||||
compelling reason to allow it in the first place. Because these functions are not
|
||||
accessible from anything except the main statement file, it would be most likely to
|
||||
want to put the functions in that file, next to the uses. The only benefit from allowing
|
||||
local functions in separate file may be as a helper file that is linked into other
|
||||
compilations. However, wrapping these utility functions in a class so they can be used
|
||||
in more places seems like a small burden with big benefits. Once wrapped in a class,
|
||||
these functions are simply methods like in C# today.
|
||||
|
||||
* Mixing classes and statements in the same file could generate some confusion. The existing
|
||||
design is that classes can see the variables created by statements, but it would be illegal
|
||||
to reference them. This keeps the option open to allow access later. However, this could be
|
||||
a confusing middle ground. To simplify the situation we could require only statements in the
|
||||
top-level in one file (forcing all types to be declared in separate files). However, there is
|
||||
interest in using utility classes in the top-level statements, perhaps especially with a
|
||||
forthcoming records feature that provides simple, short syntax for declaring new types.
|
||||
|
||||
* Many of these features mirror what we already have in CSX. It's good that our
|
||||
current designs are similar and allow these constructs in more places, but since the semantics of
|
||||
this design have subtle differences from CSX this would effectively create a third dialect of C#.
|
||||
There's some desire to unify these worlds, but it's difficult. CSX is designed to allow all
|
||||
values to be persisted, which is important for the scripting "submission" system, but this makes
|
||||
a number of types of statements illegal that we have support for in the current design, like
|
||||
ref locals. It also creates a burden for new designs, where statements need to be explicitly
|
||||
designed for both CSX and C#. For example, the new `using var` declaration form is nonsensical
|
||||
under the CSX design and probably should be illegal. Since the current 'simple programs' design
|
||||
effectively treats statements like they are part of a method body, there's a cleaner semantic
|
||||
parallel with C#, meaning less special-casing.
|
||||
|
||||
### Records
|
||||
|
||||
Here we presented a variety of different pieces of designs we have been thinking about.
|
||||
|
||||
#### Nominal Record
|
||||
|
||||
Feedback:
|
||||
|
||||
* One of the biggest drawbacks of the current writeable-property style in C#, where types are
|
||||
declared with public mutable properties that are then initialized using object initializers,
|
||||
is that author can't enforce that certain properties are always initialized. It would be a
|
||||
big disappointment if any "nominal records" feature that we built couldn't support this feature.
|
||||
|
||||
* With the design as-is there's no way to validate the whole state of the object. However, that's
|
||||
also true of the object-initializer style currently in use, and this doesn't seem to be as a big
|
||||
of a problem for current users.
|
||||
|
||||
#### Value Equality
|
||||
|
||||
* Positive feelings on generating `.Equals(T)` and implementing `IEquatable<T>`, mixed feelings
|
||||
on generating the `==` and `!=` operators.
|
||||
|
||||
* If we opt-in the whole class using `value class`, we also need an opt-out for individual members.
|
||||
Regular classes also often have somewhat specialized equality requirements, like wanting to compare
|
||||
certain lists as sequence-equal, or compare strings ignoring case. This observation points to a
|
||||
lot more customization points for value equality on general classes than value equality on records.
|
||||
|
||||
* Using value equality on mutable state is seems dangerous if the type is used in a dictionary,
|
||||
but despite the danger, other languages (Java, Go) have value equality for common types, like
|
||||
lists, that can be easily added to a dictionary.
|
||||
|
||||
* We don't currently have a robust mental model for what it means to be a "value class." Is "value
|
||||
class" a separable concept from "implementing value equality," which people often do today? Or
|
||||
is it not a different type of class, but simply a modifier signaling an implementation detail,
|
||||
namely that the compiler generates value equality automatically. If we think of value equality
|
||||
as a public contract, how does that change our view of existing code? Classes can currently
|
||||
override Equals, but we don't distinguish what *kind* of Equals they provide. That isn't a
|
||||
language concept, in a sense, but a part of the documentation.
|
||||
|
||||
### Nominal Records
|
||||
|
||||
* When using the `with` expression on nominal records, the generated parameter-less `With` method
|
||||
looks a lot like `Clone`. It does little aside from return a new object with a shallow copy of
|
||||
the state. If `With` is essentially Clone, why not use one of the existing forms of Clone that we
|
||||
already have?
|
||||
|
||||
* ICloneable is deprecated and MemberwiseClone is protected. Maybe we should just call the method
|
||||
Clone(), but not override or implement any of the other framework uses.
|
||||
|
||||
* This feature looks a lot like structural typing from other languages, like Javascript's "spread"
|
||||
operator, but that is not the feature we're currently trying to build. This is feature is still
|
||||
about declaring new types, not providing some compatibility between existing types.
|
||||
|
||||
* We spent a lot of time talking about validation and "validators", a very recent concept that was
|
||||
floated as an alternative to constructors, executing after the `with` expression.
|
||||
|
||||
* There's some general concern about having no capability of validation, but no consensus on
|
||||
exactly how validators should work.
|
||||
|
||||
* If validators work against the copied fields of the object, that seems to imply that the
|
||||
fields are the state being operated on. On the other hand, only certain members can be
|
||||
mentioned in the `with` expression. Why wouldn't those be the things that are copied? Instead
|
||||
of all the state?
|
||||
|
||||
|
||||
|
80
meetings/2020/LDM-2020-03-09.md
Normal file
80
meetings/2020/LDM-2020-03-09.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
|
||||
# C# Language Design for March 9, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Design review
|
||||
2. Records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Simple Programs
|
||||
|
||||
In the design review we covered the "simple programs" feature. A big piece of feedback is that
|
||||
they didn't like the "middle ground" we had carved out with local functions. Right now we have
|
||||
local functions that can exist both in the top-level statement "file" and also across other
|
||||
files. Because the design allows only local functions at the top level, those functions are only
|
||||
accessible from and can only access the statements in the "top-level statement file."
|
||||
|
||||
The feedback was that this would be an unfortunate design point. Organizing local functions in
|
||||
other files, when they can't be accessed by other files, is a big problem. There are two places
|
||||
we could go with this. We could either pull back and allow many of these forms only in the
|
||||
"top-level statement file." Or we could go the other way and allow more functionality at the top
|
||||
level, like allowing truly top-level members, including functions and variables, that can be
|
||||
declared and referenced from everything in the program.
|
||||
|
||||
Allowing full top-level members is attractive but opens a lot of questions. The most important is
|
||||
top-level variables. By allowing top-level variables and giving them the C# default of
|
||||
mutability, we would effectively be enabling mutable global variables. There is collective
|
||||
agreement that in everything but the smallest programs this is a bad programming practice and we
|
||||
shouldn't encourage it.
|
||||
|
||||
We could try to pivot on syntactic differences. One major difference between local variables and
|
||||
functions and member-level variables and functions is that members allow accessibility modifiers.
|
||||
Top-level statements could be, by default, local variables. Accessibility modifiers, `public`,
|
||||
`private`, `internal`, et al., could differentiate between top-level and local variables.
|
||||
However, this feels like it may be too subtle a design point, relying too much on minor syntactic
|
||||
decisions to decide things like scoping.
|
||||
|
||||
The biggest takeaway is that this is a complex topic with a lot possibilities. Maybe we could try
|
||||
to carve out a small portion to make some progress, without committing to a narrow design for the
|
||||
entire space of "top-level members." We generally like this approach, but CSX makes things
|
||||
difficult. Since the existing design focuses on local variables and local functions,
|
||||
compatibility with any sort of submission system is a problem. Fundamentally, we would need to
|
||||
decide which pieces of state in a C# program are "transient," in that they cannot be referenced
|
||||
from a new submission, and which are "preserved."
|
||||
|
||||
The value still seems important enough to move forward. There's general agreement that top-level
|
||||
statements are useful. Some people think they are useful in the simple form already presented,
|
||||
while other people want to see this as the starting point for a full feature, and these views are
|
||||
roughly compatible.
|
||||
|
||||
If we move forward with our subset, we need to flesh out the mental model for how it works.
|
||||
Specifically, it's important to note why top-level variables and functions would be inaccessible
|
||||
from inside classes. One way to think about this is the difference between instance and static.
|
||||
When you're in a static context, all the instance variables are visible, but not accessible. You
|
||||
could also model it as the statements are directly inserted into Main (which is true).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'd like to move forward with the prototype with the modification that top-level statements can
|
||||
only appear in one file. Symbols declared in these statements would be visible, but an error to
|
||||
access inside classes. All the top-level statements are treated as if they were inside an async
|
||||
Main method.
|
||||
|
||||
### Value equality
|
||||
|
||||
When we discussed records in the design meeting we brought up value equality for records and a
|
||||
proposal for extending to regular classes. A big shift was that records should have an easy
|
||||
global automatic value equality, while general classes should never have a global opt-in.
|
||||
|
||||
This seems contradictory, but if records have a set of default semantics that naturally fit value
|
||||
equality, then having it enabled by default is suitable. Value equality plays particularly well
|
||||
with immutability. Since records strongly support immutable programming, supporting value
|
||||
equality is natural. For arbitrary classes, however, it's not clear at all how value equality
|
||||
should behave. Opt-ing in either all or only some members seems to have downsides for many class
|
||||
examples.
|
||||
|
||||
We're not ruling out value equality for regular classes, but for the future we'd like to examine
|
||||
specifically how we'd like value equality to work for records. This could impact how and when we
|
||||
bring generated value equality to conventional user classes.
|
103
meetings/2020/LDM-2020-03-23.md
Normal file
103
meetings/2020/LDM-2020-03-23.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
|
||||
# C# Language Design Meeting for March 23rd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Triage
|
||||
2. Builder-based records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Triage
|
||||
|
||||
#### Generic constraint `where T : ref struct`
|
||||
|
||||
Proposal: #1148
|
||||
|
||||
This is a very complicated area. It's probably not good enough to add this generic constraint,
|
||||
because things like default interface methods create a boxed `this` parameter. It's likely that
|
||||
we would need runtime support to make this safe.
|
||||
|
||||
This is somwewhat related to the designs in the shapes/roles proposal in that it's about using
|
||||
the "shape" of an interface, possibly with more restrictions than interfaces themselves. Since
|
||||
both proposals may require runtime changes it would be valuable to batch up those changes
|
||||
together.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Push to at least C# 10.0. Should be considered in concert with the shapes/roles proposals.
|
||||
|
||||
#### Improve overload resolution for delegate compatibility
|
||||
|
||||
Proposal: #3277
|
||||
|
||||
This is a parallel to changes we previously made to betterness where we remove overloads
|
||||
that will later be considered invalid, to be removed from the overload resolution candidate
|
||||
list in the beginning. This functionally will cause more overload resolution calls to succeed,
|
||||
since invalid candidates will be removed and this will prevent overload resolution ambiguitiy.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Tentatively added to C# 9.0. We'd like this proposal for function pointers, so if we were to
|
||||
implement this for function pointers and correct overload resolution for delegates at the same
|
||||
time, that would be desirable.
|
||||
|
||||
#### Allow GetEnumerator from extension methods
|
||||
|
||||
Proposal: #600
|
||||
|
||||
First thing is to confirm that there's no compat concern. When we tried to extend the behavior
|
||||
for `IDisposable` we ran into a problem because `foreach` *conditionally* disposes things which
|
||||
match the `IDisposable` pattern. This means that if anything starts satisfying the pattern which
|
||||
didn't before, an existing method may be newly called. If we ever conditionally use the `foreach`
|
||||
pattern this would probably also be a breaking change.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Willing to accept it any time, as long as we confirm that it's not a breaking change.
|
||||
|
||||
### Builder-based records
|
||||
|
||||
When discussing records we've had various
|
||||
designs that focus on "nominal" scenarios, where the members of the record are set by name, instead of lowering into method parameters. One proposed implementation strategy is a new series of rules around initialization that we've sometimes called "initonly." We've also looked at using struct "builders" in the past for a similar purpose, and would like to revisit some of these discussions.
|
||||
|
||||
We have a proposal from a community member, @HaloFour, that lays out another example implementation strategy that we're using for discussion.
|
||||
|
||||
https://gist.github.com/HaloFour/bccd57c5e4f3261862e04404ce45909e
|
||||
|
||||
There are certainly some advantages to this structure:
|
||||
|
||||
* Uses existing valid C# to implement the pattern, making it compatible with existing compilers. If
|
||||
we avoid usage of features like `ref struct` and `in`, it could be compatible for even older
|
||||
consumers, since this would be binary compatible with C# 2.0 metadata.
|
||||
|
||||
* Allows the type being built to always see the whole set of property values being initialized,
|
||||
meaning that the author of the type can validate the new state of the object.
|
||||
|
||||
* No new runtime support for any features (e.g., does not require covariant returns)
|
||||
|
||||
There are also some disadvantages.
|
||||
|
||||
Performance could be a problem. For classes, the biggest concern is stack size. Currently,
|
||||
initializing a class with an object initializer only requires a single pointer on the class and
|
||||
then each member is initialized separately, the initializer values don't all need to be on the
|
||||
stack simultaneously. If we use a struct builder, the entire builder needs to be on the stack
|
||||
before initialization.
|
||||
|
||||
For structs, there is the extra stack space cost, but having a builder also effectively doubles
|
||||
the metadata size of the every struct. It's also potentially harder for the CLR to optimize the
|
||||
initialization and remove dead stores through the double-struct passing. If we go forward with
|
||||
this approach we should consult the JIT for their perspective.
|
||||
|
||||
We're also not sure that this approach fully eliminates all brittleness in adding/changing fields
|
||||
and properties across inheritance, especially if the inheritance is split across assemblies and
|
||||
only one author is recompiled, or they are recompiled in a different order. If we can find a way
|
||||
to close those holes, or limit the feature to prevent these situations, that could be an
|
||||
important mitigating factor.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
There's a difficult balance here. Some members are focused on about performance, some prioritize
|
||||
ecosystem compat, and others prioritize "cleanliness" of design, in different directions. Almost
|
||||
everyone has a different priority and prefers different approaches for different reasons. We need
|
||||
to discuss things more and reduce some of the unknowns.
|
117
meetings/2020/LDM-2020-03-25.md
Normal file
117
meetings/2020/LDM-2020-03-25.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
|
||||
# C# Language Design Meeting for March 25, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Questions around the new `nint` type
|
||||
|
||||
2. Target-typed new
|
||||
|
||||
## Discussion
|
||||
|
||||
Issue #3259
|
||||
|
||||
### LangVersion
|
||||
|
||||
THe question is what the behavior of the compiler should be when seeing an `nint`
|
||||
type in `langversion` C# 8. Our convention is that the compiler never preserves
|
||||
old *behavior* for older language versions. For instance, we do not preserve the
|
||||
code for older code generation strategies and switch to that with the language
|
||||
version flag. Instead, `langversion` is meant to be guard rails, providing
|
||||
diagnostics when features are used that aren't available in older versions of the
|
||||
language.
|
||||
|
||||
There are a few options we could take.
|
||||
|
||||
1. Make an exception for `nint`, allowing them to be seen and compiled like an
|
||||
`IntPtr` in `langversion` C# 8.
|
||||
|
||||
2. Make a wider divergence between `nint` and `IntPtr`. Adding a `modreq` to
|
||||
the emitted `IntPtr` type would make them effectively unusable by older language
|
||||
versions and other languages.
|
||||
|
||||
3. Preserve the behavior, as long as no new semantics are used. For instance,
|
||||
using the arithmetic operators on `nint` and on `IntPtr` have different semantics.
|
||||
It would be an error to use any of these operators in older language versions.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think (3) is the best balance.
|
||||
|
||||
### `IntPtr` and `nint` operators
|
||||
|
||||
We have two proposals:
|
||||
|
||||
1. Remove built-in identity conversions between native integers and underlying types and add explicit conversions.
|
||||
|
||||
2. Remove `nint` operators when using the `IntPtr` type
|
||||
|
||||
**Conclusion**
|
||||
|
||||
(1) is a little too harsh. Let's do (2).
|
||||
|
||||
### Behavior of constant folding
|
||||
|
||||
The concern is platform dependence.
|
||||
|
||||
In the following example
|
||||
|
||||
```C#
|
||||
const nint m = int.MaxValue;
|
||||
const nint u1 = unchecked(m + 1);
|
||||
nint u2 = unchecked(m + 1);
|
||||
```
|
||||
|
||||
if the machine is 32-bit, then the result overflows. If the machine is 64-bit, it does not.
|
||||
|
||||
While it's possible in the existing language to produce constant-folded values which are
|
||||
undefined, we don't think that behavior is desirable for nint.
|
||||
|
||||
The main contention is what to do in a `checked` context if we know the value will overflow
|
||||
32-bits. We could either produce an error, saying that this will overflow on some platforms,
|
||||
or produce a warning and push the calculation to runtime, warning that the calculation may
|
||||
overflow at runtime (and produce an exception).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Whenever we can safely produce a constant value under 32-bits, we do constant folding. Otherwise,
|
||||
the result is non-constant, and under `checked`, the code produces a warning and the result
|
||||
is non-constant.
|
||||
|
||||
### Interfaces on `nint`?
|
||||
|
||||
Should interfaces on `IntPtr` and `nint` match? Or should `nint` only accept a certain set of
|
||||
compiler-validated interfaces on `IntPtr`?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We trust that interfaces will only be added to `IntPtr` with recognition that those interfaces
|
||||
also affect `nint`. We'll make all interfaces on `IntPtr` available on `nint`, with `IntPtr`
|
||||
occurrences substituted for `nint`.
|
||||
|
||||
## Target-typed `new`
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md
|
||||
|
||||
Clarification about library evolution: if a user uses `new()`, adding a constructor to a type
|
||||
can produce an ambiguity. Similarly, if a method is called with `new()` that can produce an
|
||||
ambiguity if more overloads of that method is added. This is analogous with `null` or `default`,
|
||||
which can convert to many different types and can produce ambiguity.
|
||||
|
||||
The spec currently specifies that there are a list of types where target-typed new is allowed. To
|
||||
simplify, we propose that we specify that target-typed new should produce a fully-typed `new` and
|
||||
the legality of that expression is defined elsewhere. This does make `new()` work on enums, which
|
||||
is currently proposed as illegal because it may be confusing. However, `new Enum()` is legal
|
||||
today, so we think that it should be allowed for target-typed `new` simply because of
|
||||
consistency.
|
||||
|
||||
There's some debate on what it should do for nullable value types. On the one hand, the rule
|
||||
"new() is just shorthand for writing out the type on the left," implies that the result should be
|
||||
`null`. On the other hand, the nullable lifting rules would imply that the base type of the
|
||||
target should be the underlying type, not the nullable type. Overall, we think that `new`ing the
|
||||
underlying type makes the most sense, both because it's the most useful (we already have a
|
||||
shorthand for `null`) and because it's likely what the user intended.
|
||||
|
||||
For `dynamic`, we will not permit it simply because `new dynamic()` is also illegal.
|
||||
|
||||
Final thought: many thanks to @alrz for the great contribution!
|
112
meetings/2020/LDM-2020-03-30.md
Normal file
112
meetings/2020/LDM-2020-03-30.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
|
||||
# C# Language Design Meeting for March 30, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
1. Builders vs init-only
|
||||
|
||||
2. Possible conflicts with discriminated unions
|
||||
|
||||
3. Value equality
|
||||
|
||||
## Discussion
|
||||
|
||||
### Builders vs. init-only
|
||||
|
||||
We discussed more of the tradeoffs of using builders vs using an "init-only" feature for records,
|
||||
and looked into requirements for other languages, including VB and F#. Based on our current
|
||||
designs, the work needed to consume the new features for "init-only" seem fairly small.
|
||||
Recognizing the `modreq` and allowing access to init-only members, and calling any necessary
|
||||
"validator" on construction, are pretty simple features for VB. VB already has the syntactic
|
||||
requirements for the feature (object initializers), and we'd like to keep it possible for VB to
|
||||
consume major changes in the API surface, if not write those new features. F# is undergoing
|
||||
active development and changes are certainly viable there. Because the scope of changes is more
|
||||
open-ended in F#, it's possible it could feature more implementation work.
|
||||
|
||||
Notably, most of the features associated with "init-only" and "validators" do not require a new
|
||||
runtime, only a new compiler version. The new compiler version is necessary to recognize safety
|
||||
rules (validators must always be called after constructors, if they exist), but they don't use
|
||||
any new features in the CLR. The only feature potentially requiring runtime features is
|
||||
overriding a record with another record, which could potentially require covariant returns.
|
||||
|
||||
The remaining differences seem to come down to whether you can "see" the whole object during
|
||||
initial construction (as opposed to validation). If you can see the whole object immediately,
|
||||
that makes writing a `With` method that avoids a copy if all the values are identical very
|
||||
straightforward. However, this could be done for "init-only" as well, by moving this semantic to
|
||||
the `with` expression, optionally comparing the arguments to the `With` expression with the
|
||||
values on the receiver object and avoiding calling With if they are identical. Therefore we don't
|
||||
think we're actually ruling anything out by going down the "init-only" route.
|
||||
|
||||
There are advantages in going down the "init-only" route instead. The performance for structs
|
||||
is probably better and more optimizable, and the IL pattern seems clearer and less bloated.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the "init-only" IL better. Given the path forward for other languages and compatibility
|
||||
with many runtimes, we think it's a better future as an IL pattern.
|
||||
|
||||
### Conflicts with discriminated unions
|
||||
|
||||
There was a general question if these decisions could impact a future discriminated unions feature.
|
||||
We don't have a lot in mind, but if we do end up building a discriminated union made of the records
|
||||
feature, there is one component we may want to reserve. Discriminated unions often have a set of simple
|
||||
data members. For instance, if we wrote a Color enum as a discriminated union, it could look like.
|
||||
|
||||
```C#
|
||||
enum class Color
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue
|
||||
}
|
||||
```
|
||||
|
||||
If we reduce these to records, it may look like:
|
||||
|
||||
```C#
|
||||
abstract class Color
|
||||
{
|
||||
public class Red() : Color;
|
||||
public class Green() : Color;
|
||||
public class Blue() : Color;
|
||||
}
|
||||
```
|
||||
|
||||
The problem is: since those classes are effectively singletons, we'd like for the instances to be
|
||||
cached by the compiler, to avoid allocations. However, we don't currently have a syntax in C# that
|
||||
means "singleton." Scala uses the "empty record" syntax to mean singleton. We need to decide if we'd
|
||||
like to reserve that syntax ourselves, or find some other solution.
|
||||
|
||||
### Value equality
|
||||
|
||||
We've decided that we want value equality by default for records. We need to settle on what that
|
||||
means. The primary proposal on the table is shallow-field-equality, namely comparing all fields
|
||||
in the type via EqualityComparer. This would match the semantic we have decided on for "Withers,"
|
||||
where the shallow state of the object is copied, similar to MemberwiseClone.
|
||||
|
||||
There's a large segment of the LDM that thinks doing anything except for field comparison is
|
||||
problematic because it introduces far too many customization points for the record feature.
|
||||
Almost all custom equality would have to deal with sequence equality and string equality, which
|
||||
are already very different mechanisms. There's a proposal that we could provide further
|
||||
customization via source generators, which could allow almost any customization.
|
||||
|
||||
A follow-up to that is: why have value equality by default at all? Why not use source generators
|
||||
for all value equality? One problem with that is that we want the simplest records to be very
|
||||
short. The other problem is that we effectively have to pick an equality (C# will inherit one if
|
||||
you don't). We previously decided that value equality is a non-controversial default -- if it's
|
||||
wrong it's probably not worse than when reference equality is wrong, and it's often better.
|
||||
Struct-like field-based equality is a simple and familiar version of value equality.
|
||||
|
||||
We also need to decide on value-equality as a separable feature for regular classes. Some people
|
||||
like the idea of a separate feature and would be fine even with the constrained version that only
|
||||
compares fields. Others don't think this feature meets the hurdles for a new language feature. If
|
||||
we don't have customization options, it may be rarely used, and it seems possible that a source
|
||||
generator version could be the much more popular version. It's also worth noting that, as a
|
||||
separable feature, we don't need to add separable equality now.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
No separable value equality for non-records, right now. Default record equality is defined as
|
||||
field-based shallow equality.
|
119
meetings/2020/LDM-2020-04-01.md
Normal file
119
meetings/2020/LDM-2020-04-01.md
Normal file
|
@ -0,0 +1,119 @@
|
|||
|
||||
# C# Language Design Meeting for April 1, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Function pointer calling conventions
|
||||
|
||||
2. `field` keyword in properties
|
||||
|
||||
## Discussion
|
||||
|
||||
### Function Pointers
|
||||
|
||||
There are a few open issues and proposals for generalization of function pointers based on
|
||||
new runtime features.
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3324
|
||||
|
||||
#### NativeCallableAttribute
|
||||
|
||||
The attribute already exists in the CLR, and allows for specifying a calling convention other than
|
||||
`managed` (which means that it can't be called from C#, but could be used from function pointers).
|
||||
|
||||
The question is what level of support we want to provide in the language for this attribute.
|
||||
|
||||
Since the runtime behavior is to crash if the method is called incorrectly (meaning, invoked at
|
||||
all from C# if not through a function pointer), we almost certainly want to recognize the
|
||||
attribute's existence and provide errors for incorrect usage.
|
||||
|
||||
We considered more restrictions than the ones mentioned in the issue (only usable in function
|
||||
pointers, parameters must be blittable, must be static) like restricted accessibility or special
|
||||
syntax. The consensus is that is too much work for a small feature.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
`NativeCallableAttribute` should be recognized and the restrictions are accepted, with the
|
||||
addition that generics are also prohibited in the method and all containing types, recursively,
|
||||
and delegate conversion is also prohibited.
|
||||
|
||||
#### Supporting extra calling conventions
|
||||
|
||||
The existing proposal mandates that the only the existing calling conventions are supported. We
|
||||
previously said we'd consider new calling conventions when they were proposed by the runtime. We now
|
||||
have proposals about some likely new calling convention from the runtime.
|
||||
|
||||
The proposal is that the "calling convention" syntax in function pointers could be a general identifier, and legal values for the runtime would be determined by name matching against type names
|
||||
starting with `CallConv` in a particular namespace, and passing through Unicode lower-case mapping.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted.
|
||||
|
||||
#### Attributes on function pointer types
|
||||
|
||||
The syntax is getting a bit verbose, but allowing an extra axis for customization seems like the
|
||||
simplest extension of function pointers that provides the level support that the runtime may need
|
||||
in the future.
|
||||
|
||||
Currently our favored syntax is:
|
||||
|
||||
```C#
|
||||
delegate* cdecl[SuppressGCTransition, MyFuncAttr]<void> ptr;
|
||||
```
|
||||
|
||||
The attribute-like syntax would be turned into the `modreq`s on the function pointer type that
|
||||
would be used by the runtime to encode special calling behavior. These would also effectively be
|
||||
different types at the C# level and would not have implicit conversions between them.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted, assuming there are no problems in implementation.
|
||||
|
||||
### `field` keyword in properties
|
||||
|
||||
Over the years there have been many requests for similar features, e.g.
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
Maybe the simplest version is that there is a contextual identifier, e.g. `field`, which refers
|
||||
to an implicit backing field of the property. This seems useful, but a big limitation is that the
|
||||
backing field of the property must have the same type as the property. If lazy initialization is
|
||||
a common case, that seems likely to require differing property types, as the backing field would
|
||||
often be nullable, but the initialized field would be not nullable.
|
||||
|
||||
On the other hand, biting off too much in a single feature may delay simple scenarios
|
||||
unnecessarily. Should we try to address the simplest scenarios first, and leave more complex
|
||||
scenarios for later? In this case we probably need to find what scenarios are served by that
|
||||
design. Two things that are recognized are simple validation, e.g.
|
||||
|
||||
```C#
|
||||
public int PositiveValue
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentException("Cannot be negative")
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and registration like `INotifyPropertyChanged`
|
||||
|
||||
```C#
|
||||
public int P
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
PropertyChanged();
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's confirm that these scenarios are the ones most commonly requested and that they aren't
|
||||
addressed or modified by any of the other scheduled language features, e.g. source generators for
|
||||
INotifyPropertyChanged. From there we can discuss the specific proposal with a better
|
||||
understanding of the problem and solution space.
|
86
meetings/2020/LDM-2020-04-06.md
Normal file
86
meetings/2020/LDM-2020-04-06.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
|
||||
# C# Language Design Notes for April 6th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Init-only members
|
||||
|
||||
## Discussion
|
||||
|
||||
We have a proposal to dive into: https://github.com/jaredpar/csharplang/blob/init/proposals/init.md
|
||||
|
||||
* The proposal notes that you can set `init` fields of the base type during construction, similar
|
||||
to `readonly`. This is not how `readonly` works today, only the declaring type can set readonly
|
||||
fields
|
||||
|
||||
* The proposal allows `init` on class and struct declarations as a shorthand for `init` on types.
|
||||
This is different from how `readonly` struct works today, where there is no syntactic shorthand,
|
||||
`readonly` simply adds the additional requirement that all instance fields are marked `readonly`.
|
||||
|
||||
* For the runtime: does this feature prohibit runtime restrictions on setting `readonly` instance
|
||||
fields in the future? Put simply: yes. To avoid breaking C#, the runtime would be required to
|
||||
either respect the proposed `InitOnlyAttribute`, or restrict optimizations to not alter the code
|
||||
for these features.
|
||||
|
||||
* Use in interfaces: the proposal prohibits it, but the following example seems useful:
|
||||
|
||||
```C#
|
||||
interface I
|
||||
{
|
||||
int Prop { get; init set; }
|
||||
}
|
||||
|
||||
public void MyMethod<T>() where T : I, new()
|
||||
{
|
||||
var t = new T() {
|
||||
Prop = 1
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
* Signature compatibility: should `init` properties be compatible with mutable ones? That is, should
|
||||
removing `init` in favor of a bare `set` be binary-compatible? This impacts our decisions for how
|
||||
we think about safety in older compilers:
|
||||
|
||||
* If we use a modreq to prevent other compilers from unsafely using a `setter`, that affects the
|
||||
signature of the method, and would make the above a breaking change
|
||||
|
||||
* If we want accept that older compilers are not a problem (C#, VB, and F# will all be updated),
|
||||
perhaps we don't need to specially guard this at all
|
||||
|
||||
* We could use attributes to mark *and* guard, by using the `Obsolete` attribute. `ref struct`s
|
||||
use this guard by having an Obsolete attribute with a reserved message, that is ignored by
|
||||
compatible compilers.
|
||||
|
||||
### Accessors
|
||||
|
||||
Should we allow three accessors: `get, set, init`? A big problem here is that you can end up
|
||||
calling instance members in a constructor that invoke the setter, not the initter, for a
|
||||
property. This means that there are few, if any, invariants that hold for init vs. set, and
|
||||
weakens the feature significantly.
|
||||
|
||||
### Syntax
|
||||
|
||||
`init` vs. `initonly` for syntax. On the one hand, `init` is inconsistent with `readonly` (vs. `initonly`), but on the other hand we're pretty sad that `readonly` is such a long keyword. It also has few analogies
|
||||
with properties, where `readonly` isn't allowed at all, and the shorter `init` keyword seems more similar to
|
||||
`get` and `set`
|
||||
|
||||
* Usage of `init` as a modifier: there are a number of different meanings here, and similarities
|
||||
between `readonly` and `init` is separating the more places we use it. For instance, if `init` is
|
||||
allowed as a member modifier, it seems similar to `readonly` on members, but they actually do
|
||||
different things. Moreover, `readonly` on types means that all instance fields and methods are
|
||||
`readonly`, while `init` on types would only mean `init` on fields, not on methods.
|
||||
|
||||
* What are the uses for `init` methods, aside from helper methods? Could they be used in collection initializers?
|
||||
|
||||
### Required Initialization
|
||||
|
||||
The proposal in this doc is that required initialization is also an important feature for setters. We're
|
||||
going to leave that discussion for a future meeting. As proposed, nullable warnings are not modified for
|
||||
`init` members.
|
||||
|
||||
**Conclusions**
|
||||
|
||||
No `init` on types. No agreement on syntax. We probably have to talk about more of the use cases.
|
||||
We've decided that having three different accessors is not helpful. Settled that we will place a
|
||||
`modreq` on all `init` setters.
|
154
meetings/2020/LDM-2020-04-08.md
Normal file
154
meetings/2020/LDM-2020-04-08.md
Normal file
|
@ -0,0 +1,154 @@
|
|||
|
||||
# C# Language Design for April 8, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `e is dynamic` pure null check
|
||||
2. Target typing `?:`
|
||||
3. Inferred type of an `or` pattern
|
||||
4. Module initializers
|
||||
|
||||
## Discussion
|
||||
|
||||
### `e is dynamic` pure null check
|
||||
|
||||
We warn that doing `e is dynamic` is equivalent to `e is object`, but `e is object` is a pure
|
||||
null check, while `e is dynamic` is not. Should we make `is dynamic` a pure null check for
|
||||
consistency?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Yes.
|
||||
|
||||
### Target-typing `?:`
|
||||
|
||||
The simplest example where we have a breaking change is
|
||||
|
||||
```C#
|
||||
void M(short)
|
||||
void M(long)
|
||||
M(b ? 1 : 2)
|
||||
```
|
||||
|
||||
Previously this would choose `long`, because the expressions are effectively typed separately,
|
||||
and when inferring `1 : 2` we would choose `int`, and then rule out `short` during overload
|
||||
resolution as invalid.
|
||||
|
||||
Target-typing converts each arm in turn to find the best possible type, selecting `short` instead
|
||||
of `long`. That means the first overload is selected instead with target-typing.
|
||||
|
||||
It's hard to easily avoid this breaking change. The obvious mechanism, running overload
|
||||
resolution twice, is problematic because having multiple arguments with `conditional` expressions
|
||||
produces exponential growth in the number of passes of overload resolution.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's talk about this again in a separate meeting. Since whatever we choose here will probably
|
||||
be the final decision (any further changes will be breaking), we want to be sure we're making the
|
||||
best choice.
|
||||
|
||||
### Inferred type of an `or` pattern is the common type of two inferred types
|
||||
|
||||
```C#
|
||||
object o = 1;
|
||||
if (o is (1 or 3L) and var x)
|
||||
// what is the type of `x`?
|
||||
```
|
||||
|
||||
The problem here is that the existing common type algorithm allows conversion that are forbidden
|
||||
in pattern matching. In the above case we would choose `long`, because `3L` is a long, and `1`
|
||||
can be converted to `long`. However, in pattern matching the widening conversion from `int` to
|
||||
`long` is illegal.
|
||||
|
||||
The proposal is to narrow the set of conversions only to implicit reference conversions or
|
||||
boxing conversions. Why this could be useful is the example
|
||||
|
||||
```C#
|
||||
class B { }
|
||||
class C : B { }
|
||||
if (o is (B or C) and var x)
|
||||
// x is `B`
|
||||
```
|
||||
|
||||
A follow-up question is about ordering. The proposed rules only infer types mentioned in
|
||||
the checks, meaning that the following
|
||||
|
||||
```C#
|
||||
if (o is (Derived1 or Derived2 or Base { ... }) and var x)
|
||||
```
|
||||
|
||||
looks like this today
|
||||
|
||||
```C#
|
||||
((Derived1 or Derived2) or Base)
|
||||
|
||||
-> ((object) or Base)
|
||||
|
||||
-> (object)
|
||||
```
|
||||
|
||||
On the other hand, if the example were parameterized in the opposite way,
|
||||
|
||||
```C#
|
||||
(Derived1 or (Derived2 or Base))
|
||||
|
||||
-> (Derived1 or (Base))
|
||||
|
||||
-> (Base)
|
||||
```
|
||||
|
||||
So the ordering seemingly matters if the `or` pattern is a simple binary expression.
|
||||
|
||||
The first question is if
|
||||
|
||||
```C#
|
||||
if (o is (Derived1 or Derived2 or Base { ... }))
|
||||
```
|
||||
|
||||
should produce `Base`, and the second question is if parenthesizing affects this, e.g. explicitly saying
|
||||
|
||||
```C#
|
||||
if (o is ((Derived1 or Derived2) or Base { ... }))
|
||||
```
|
||||
|
||||
produces `object`.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're not seeing a lot of scenarios that depend on these features, but not doing it feels like
|
||||
leaving information on the table. Functionally, the compiler can infer a stronger type, so why
|
||||
not do so? The proposed modified common type algorithm is accepted.
|
||||
|
||||
We also think that type narrowing for the `or` operation should use all the `or` operations
|
||||
in a series as the arguments to the common type algorithm.
|
||||
|
||||
### Module Initializers
|
||||
|
||||
Proposal: https://github.com/RikkiGibson/csharplang/blob/module-initializers/proposals/module-initializers.md
|
||||
|
||||
There are a few places where module initializers today. The most common is a shared resource
|
||||
that is used by multiple types, but the program would like to initialize the shared resource
|
||||
before the static constructors of the types are run.
|
||||
|
||||
The question for the implementation is to how to indicate where the code for the module
|
||||
initializer will go, and how the compiler will recognize it.
|
||||
|
||||
The proposal provides a mechanism to identify the module initializer via an attribute on the module
|
||||
that points to a type, and type contains a static constructor that acts as the module initializer.
|
||||
From a conceptual level, this seems more complicated than necessary. Can we put the attribute on the
|
||||
type or, even the method, that holds the code for the module initializer?
|
||||
|
||||
If we go that route, why require the code be in the static constructor at all? Can any static method
|
||||
be a target? One reason why we may prefer a static constructor is that if the emitted module initializer
|
||||
calls the target static constructor method, it's easy to insert code before that method is invoked
|
||||
by writing a static constructor for the containing type. On the other hand, even static constructor
|
||||
can have code inserted before them, for example with static field initializers.
|
||||
|
||||
The last question is whether to allow only one module initializer method, or allow multiple and specify
|
||||
that the compiler will call them in a stable, but implementation-defined order.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's let any static method be a module initializer, and mark that method using a well-known attribute.
|
||||
We'll also allow multiple module initializer methods, and they will each be called in a reserved, but
|
||||
deterministic order.
|
77
meetings/2020/LDM-2020-04-13.md
Normal file
77
meetings/2020/LDM-2020-04-13.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# C# Language Design for April 13, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Roadmap for records
|
||||
2. Init-only properties
|
||||
|
||||
|
||||
# Roadmap for records
|
||||
|
||||
We want to break down the records feature in a way that gets incremental steps out to partner teams and users sooner, and lets us iterate based on feedback. So what order should we do things in?
|
||||
|
||||
Two demanding aspects of records that are certainly important but can probably be done later are:
|
||||
|
||||
1. Primary constructors (allowing positional parameters directly on record types)
|
||||
2. Inheritance to and from record types
|
||||
|
||||
So a proposal is to split those off in the first iteration of implementation.
|
||||
|
||||
## Decision
|
||||
|
||||
We do need to get those right to ultimately ship the feature! However, we want to start by building a version of records that:
|
||||
* Is nominal only (no primary constructor)
|
||||
* Inherits from object and can't be inherited from
|
||||
* Special-cases `with` expressions to `With` methods rather than rely on a "Factory" feature
|
||||
* Fully implements value equality
|
||||
|
||||
For this to be useful we need the init-only property feature at the same time as well, so that there is *some* way to create and initialize immutable records.
|
||||
|
||||
In subsequent iterations we will
|
||||
* Add support for inheritance to records
|
||||
* Add primary constructors to records
|
||||
* Generalize the `With` implementation to use factories
|
||||
* Decide on and embrace defaults (`public` by default?) and abbreviations
|
||||
|
||||
On parallel tracks we will work on:
|
||||
* Factory methods
|
||||
* Validators?
|
||||
* Mandatory properties?
|
||||
* Primary constructors as a general feature?
|
||||
|
||||
|
||||
# Init-only members
|
||||
|
||||
Init-only properties are the most urgent separate feature to nail down, as the experience of even the first iteration of records depends on them. There are a couple of design decisions still left open; we address them here.
|
||||
|
||||
## Should we have init-only fields?
|
||||
|
||||
Readonly fields today can only be assigned during construction, and only through a `this` access within the body of the class that declares the field. As we extend the concept of "initialization time" to cover execution of init-only setters (by object initializers in client code) as well as validators (if we add those to the language), it makes sense that `readonly` fields should be assignable from within those kinds of members as well.
|
||||
|
||||
We would *not* allow readonly fields to be directly assigned in an object initializer, however, as that would undermine the expectation that the class author has full control over how and when they are assigned.
|
||||
|
||||
Allowing init-only properties to assign readonly fields would address most of the init-only scenario: An object initializer can be used to set immutable state on the newly created object. A dedicated init-only form of fields is not needed for that. It probably *does* have valid scenarios (in programming styles where immutable fields are themselves public), but those seem less central. We could wait see if that rises to the importance of a seperate, subsequent feature.
|
||||
|
||||
### Decision
|
||||
|
||||
Let's allow init-only property setters (and validators, if and when we add them) to assign to `readonly` fields of the same object.
|
||||
|
||||
Let's not add init-only fields now. We can consider a new kind of field that can be initialized directly in object initializers later, as a separate feature, if we become convinced that it's worthwhile.
|
||||
|
||||
## Syntax
|
||||
|
||||
Should the setters of init-only properties be called `init` or `init set`? In other words, is `init` a modifier on the `set` accessor, or does it replace it completely?
|
||||
|
||||
Shorter is generally nicer. However, we do foresee a (near?) future where `init` as a modifier could be applied to other members and accessors, to mean that they can only run during initialization (and in return would get privileges such as assigning to readonly members).
|
||||
|
||||
### Decision
|
||||
|
||||
`init` it is. `init` as a modifier is an interesting feature, but we can discuss it separately. If we do, we can consider allowing `init set` as a long form of an init-only setter for regularity when code has `init` modifiers in multiple places. But for now, let's just allow `init` instead of `set`.
|
||||
|
||||
## Init-only indexers
|
||||
|
||||
Indexers also have `set` accessors. Should they also be allowed to declare an `init` accessor instead?
|
||||
|
||||
### Decision
|
||||
|
||||
It makes perfect sense, is somewhat useful, would be more regular in the language, and has straightforward semantics. Let's do it.
|
74
meetings/2020/LDM-2020-04-15.md
Normal file
74
meetings/2020/LDM-2020-04-15.md
Normal file
|
@ -0,0 +1,74 @@
|
|||
# C# Language Design Notes for Apr 15, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Non-void and non-private partial methods
|
||||
2. Top-level programs
|
||||
|
||||
# Non-void, non-private partial methods
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/3301.
|
||||
|
||||
Currently, partial methods are required to return void. They are implicitly private, and cannot have an explicit accessibility. In return for that, calls to a partial method that has a *declaration* but no *definition* can be safely elided by the compiler. Thus, partial methods serve as optional "hooks" or points of extension for generated code, code that is conditionally included based on compilation target, etc.
|
||||
|
||||
With the expected advent of source generators in the C# 9.0 timeframe, there are likely to many scenarios where these restrictions are too limiting. The proposal suggests a different trade-off for partial methods, where they *can* have return values, and *can* have broader accessibility, but in exchange the definition is *mandatory*: An implementation *must* be provided, since calls can't be elided.
|
||||
|
||||
The mandatory aspect can in fact be viewed as a feature. It is a way for one part of the code to *require* another part to be specified, even as they are separated across files and authorship.
|
||||
|
||||
Main concern is that we would need to preserve the "old" semantics for compatibility in cases that are already allowed in C#, and developers may accidentally fall into that case, failing to compel another part to produce an implementation, and having calls elided without wanting to.
|
||||
|
||||
One mitigating factor is that the existing feature doesn't allow you to explicitly say `private` - it has to be implied. So we could say that if `private` is explicitly supplied we are in the new semantics, and the method implementation is required. It's a subtle an non-obvious distinction, but at least it is there.
|
||||
|
||||
Another question is whether we would allow other members to be partial. We would need to work out the syntax in each case: E.g. how do you distinguish a partial property definition from a declaration that implements it as an auto-property?
|
||||
|
||||
## Decision
|
||||
|
||||
Despite the weirdness of distinguishing between implicit and explicit private (the latter requires an implementation, the former does not), we are ok with accepting this wart in the language. The feature extension is valuable, and alternative solutions are distinctly less appetizing.
|
||||
|
||||
On the other hand we are not ready to allow `partial` on other kinds of members. If future scenario bear out a strong need, we will do the design work to hash it out, but we think methods are able to address the vast majority of what's needed.
|
||||
|
||||
# Top-level statements
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/Simple-programs.md
|
||||
|
||||
We took a look at the currently implemented semantics to make sure we are happy with them. A couple of questions came up:
|
||||
|
||||
## Expressions at the end
|
||||
|
||||
Part of the motivation for the feature was to decrease the syntactic distance between C# (.cs) and its scripting dialect (.csx). However, unlike script we still don't allow expressions at the end. For the scripting dialect this is mostly for producing a result in an interactive setting.
|
||||
|
||||
### Decision
|
||||
|
||||
We are ok with this remaining distance, and would prefer not to have a notion of "expression at the end produces result" in C#.
|
||||
|
||||
## Shadow and error
|
||||
|
||||
The proposal puts top-level local variables and functions in scope inside type declarations in the program. However, if they are are used in those places, and error is given.
|
||||
|
||||
### Decision
|
||||
|
||||
This is deliberately there to allow us to do a more general form of top-level functions in the future. We do believe that it protects likely future designs for this.
|
||||
|
||||
## Args
|
||||
|
||||
Currently there is no way to access the `args` array optionally given as input to an explicit `Main` method. Instead you have to make use of existing APIs that have a slightly different behavior (they include the name of the program as the first element), and certainly look different.
|
||||
|
||||
For anyone who uses the APIs in a top level program, they can still trivially move it into an explicit `Main` method at a later point, but going the other way with a `Main` body that uses `args` is not so easy.
|
||||
|
||||
There are ideas to:
|
||||
- add a new API that looks more like `args` (e.g. `Something.Args`) and behaves the same way
|
||||
- add `args` as a magic variable in top level programs (similar to `value` in property setters), on the assumption that 99.9% of `Main` methods use `args` as the parameter name.
|
||||
|
||||
### Decision
|
||||
|
||||
We think this is important to pursue further, but aren't going to hold up the feature for it.
|
||||
|
||||
## Await triggers a different signature
|
||||
|
||||
In the current implementation, the signature of the `Main`-like method generated from the top level program will be different, depending on whether `await` is used or not. If it is, then the signature will include `async Task<...>`, otherwise it won't.
|
||||
|
||||
An alternative would be to always generate a `Task`-based signature, and just suppress the usual warning when no `await`s occur in the body. The choice doesn't affect the user much. The main difference is that with the current design there is no need to reference the `Task` types, and any limitations imposed by the language inside async methods are not in force, unless `await` is used.
|
||||
|
||||
### Decision
|
||||
|
||||
We stick with the current design.
|
64
meetings/2020/LDM-2020-04-20.md
Normal file
64
meetings/2020/LDM-2020-04-20.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
|
||||
# C# Language Design Meeting for April 20, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records:
|
||||
|
||||
1. Factory methods
|
||||
|
||||
## Discussion
|
||||
|
||||
The proposal at its core is to allow certain methods to opt-in to certain language semantics
|
||||
that are only currently valid at construction, namely object initializers, collection
|
||||
initializers, and the new `with` expression (although that expression is legal on only certain
|
||||
factory methods).
|
||||
|
||||
Possible extension of the feature: allow initializer forms everywhere, but only allow `init`
|
||||
properties to be initialized when the method is marked with `Factory`. However, almost all uses
|
||||
of this syntax would be a mutation of the receiver, and it may not be clear that the initializer
|
||||
syntax produces a mutation.
|
||||
|
||||
As to whether `null` should be a valid return: most people think no. Since almost all initializer
|
||||
forms dereference the receiver, this is essentially creating a very obscure way of producing
|
||||
exceptions. In addition, all struct values should be permitted, as they are all safe. `default`
|
||||
should be legal if the target type is known to be a struct. We have not considered what the
|
||||
behavior should be for unconstrained generics.
|
||||
|
||||
There also some concerns about the syntactic extensions. First in that this would make `identifier {
|
||||
... }` a valid syntactic form in most situations. This may not be syntactically ambiguous today,
|
||||
but we have a lot of different features, like `block expressions`, which share some syntactic
|
||||
similarity. Even if there is no syntactic ambiguity, some people are still concerned that the feature
|
||||
will be too opaque. One way to remedy this would be to require the `new` keyword for this form as well.
|
||||
So the new syntax would be:
|
||||
|
||||
```C#
|
||||
var s = new Create() { Name = "Andy" };
|
||||
```
|
||||
|
||||
There could be some naming ambiguity here because `Create` could be either a factory method or a
|
||||
type name. We would have to preserve the interpretation as a type here for compatibility.
|
||||
|
||||
There's a broader question of how or if we'd like a general initializer feature. There's some
|
||||
question of whether the feature is useful enough to deserve the complexity at all, using any
|
||||
additional syntax. Alternatively, we could embrace the syntax requiring the `new` keyword.
|
||||
|
||||
One important piece of history is that initializers are not meant for mutating existing state,
|
||||
only for mutating new objects. This doesn't necessarily conflict with allowing initializers on
|
||||
any object, but the reason here is not that the language is suggesting using object initializers
|
||||
for arbitrary mutation, but that convention alone is good enough to promote use on "new" objects
|
||||
only.
|
||||
|
||||
Regardless of the extensions of the feature, we certainly need to implement something for
|
||||
records. The core feature requirement here is for the `with` expression, which needs to assign to
|
||||
`init` fields. We can head two directions: special case the `Clone` method, or build a more general
|
||||
feature. This is a spectrum, where one end may be a new syntactic form specific to just the Clone
|
||||
method, and the other end could be a `Factory` attribute that could be applied to any method.
|
||||
|
||||
### Conclusion
|
||||
|
||||
Right now we're more concerned with what to do for records. In the meantime, let's not support
|
||||
user-written Clone methods. A Clone method will be generated for a record with an unspeakable type
|
||||
and the SpecialName flag. The `with` expression will look for exactly that method name. We intend
|
||||
to decide for C# 9 how that method will be written in source. We'll consider broader `Factory`
|
||||
scenarios later.
|
180
meetings/2020/LDM-2020-04-27.md
Normal file
180
meetings/2020/LDM-2020-04-27.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
|
||||
# C# Language Design Meeting for April 27, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Records: positional
|
||||
|
||||
## Discussion
|
||||
|
||||
The starting point for positional records is how it fits in with potential
|
||||
"primary constructor" syntax. The original proposal for primary constructors
|
||||
allowed the parameters for primary constructors to be visible inside the class:
|
||||
|
||||
```C#
|
||||
class MyClass(int x, int y)
|
||||
{
|
||||
public int P => x + y;
|
||||
}
|
||||
```
|
||||
|
||||
When referenced, `x` and `y` would be like lambda captures. They would be in
|
||||
scope, and if they are captured outside of a constructor, a private backing
|
||||
field would be generated.
|
||||
|
||||
One consequence of this design is that the primary constructor must *always*
|
||||
run. Since the parameters are in scope throughout the entire class, the primary
|
||||
constructor must run to provide the parameters. The proposed way of resolving
|
||||
this is to require all user constructors to call the primary constructor, instead
|
||||
of allowing calls to `base`. The primary constructor itself would be the only
|
||||
constructor allowed (and required) to call `base`.
|
||||
|
||||
This does present a conundrum for positional records. If positional records support
|
||||
the `with` expression, as we intended for all records, they must generate two constructors:
|
||||
a primary constructor and a copy constructor. We previously specified that the copy
|
||||
constructor works off of fields, and is generated as follows
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
protected C(C other)
|
||||
{
|
||||
field1 = other.field1;
|
||||
...
|
||||
fieldN = other.fieldN;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This generated code violates the previous rule: it doesn't call the primary constructors.
|
||||
One way to resolve this would be to change the codegen to delegate to the primary constructor:
|
||||
|
||||
```C#
|
||||
record class Point(int X, int Y)
|
||||
{
|
||||
protected Point(Point other) : Point(other.X, other.Y) { }
|
||||
}
|
||||
```
|
||||
|
||||
This is almost identical, except that the primary constructor may have side-effects, or the
|
||||
property accessors may have side-effects, if user-defined. We had strong opinions against using
|
||||
the accessors before because of this -- we couldn't know if the properties were even
|
||||
auto-properties and whether we were duplicating or even overwriting previous work.
|
||||
|
||||
However, we note that violating the rule for our generated code shouldn't be a problem in
|
||||
practice. Since the new object is a field-wise duplicate of the previous object, if we assume
|
||||
that the previous object is valid, the new object must be as well. All fields which were
|
||||
initialized by the primary constructor _must already be initialized_. Thus, for our code
|
||||
generation it's both correct and safer to keep our old strategy. For user-written constructors
|
||||
we can require that they call the primary constructor, but because the user owns the type, they
|
||||
should be able to provide safe codegen. In contrast, because the compiler doesn't know the full
|
||||
semantics of the user type, we have to be more cautious in our code generation.
|
||||
|
||||
This doesn't really contradict with our goal of making a record representable as a regular class.
|
||||
A mostly-identical version can be constructed via chaining as described above. The only
|
||||
difference is in property side effects, which the compiler itself can’t promise is identical, but
|
||||
if it were written in source then the user could author their constructor to behave similarly.
|
||||
|
||||
Property side-effects have an established history of being flexible in the language and the
|
||||
tooling. Property pattern matching doesn't define the order in which property side effects are
|
||||
evaluated, doesn't promise that they even will be evaluated if they’re not necessary to determine
|
||||
whether the pattern matches, and doesn't promise that the ordering will be stable. Similarly, the
|
||||
debugger auto-evaluates properties in the watch window, regardless of side effects, and the
|
||||
default behavior is to step over them when single stepping. The .NET design guidelines also
|
||||
specifically recommend to not have observable side effects in property evaluation.
|
||||
|
||||
We now have a general proposal for both how positional records work, and how primary constructors
|
||||
work.
|
||||
|
||||
Primary constructors work like capturing. The parameters from the primary constructor are visible
|
||||
in the body of the class. In field initializers and possibly a primary constructor body, they are
|
||||
non-capturing, namely that use of the parameter does not capture to any fields. Everywhere else
|
||||
in the class, the parameters are captured and create a private backing field.
|
||||
|
||||
Positional records work like primary constructors, except that they also generate public
|
||||
init-only properties for each positional record parameter, if a member with the same signature
|
||||
does not already exist. This means that in field initializers and the primary constructor body,
|
||||
the parameter name is in scope and shadows the properties, while in other methods the parameter
|
||||
name is not in scope. In addition, the generated constructor will initialize all properties with
|
||||
the same names as the positional record parameters to the parameter values, unless the
|
||||
corresponding members are not writeable.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The above proposals are accepted. Both positional records and primary constructors are accepted
|
||||
with the above restrictions. In source, all non-primary constructors in a type with a primary
|
||||
constructor must call the primary constructor. The generated copy constructor will not be
|
||||
required to follow this rule, instead doing a field-wise copy. The exact details of the scoping
|
||||
rules, including whether primary constructors have parameters that are in scope everywhere, or
|
||||
simply generate a field that is in scope and shadows the parameter, is an open issue.
|
||||
|
||||
### Primary constructor bodies and validators
|
||||
|
||||
We do have a problem with some syntactic overlap. We previously proposed that our original
|
||||
syntax for primary constructor bodies could be the syntax for a validator. However, there
|
||||
are reasons why you may want to write both. For instance, constructors are a good way to
|
||||
provide default values for init-only properties that may be overwritten later. Validators
|
||||
are still useful for ensuring that the state of the object is legal after the init-only.
|
||||
In that case we need two syntaxes that can be composed. The proposal is
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y)
|
||||
{
|
||||
public TypeName
|
||||
{
|
||||
// constructor
|
||||
}
|
||||
|
||||
init
|
||||
{
|
||||
// validator
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To mirror the keyword used for init-only properties, we could use the `init` keyword
|
||||
instead. This would also hint that validators aren't *only* for validating the state,
|
||||
they can also set init-only properties themselves. To that end, we have a tentative name:
|
||||
final initializers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted. `type-name '{' ... '}'` will refer to a primary-constructor-body and `init '{' ... '}'` is
|
||||
the new "validator"/"final initializer" syntax. No decisions on semantics.
|
||||
|
||||
### Primary constructor base calls
|
||||
|
||||
Given that we have accepted the following syntax for primary constructors and primary constructor bodies,
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y)
|
||||
{
|
||||
public TypeName
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
how should we express the mandatory base call? We have two clear options:
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y) : BaseType(X, Y);
|
||||
|
||||
class TypeName(int X, int Y) : BaseType
|
||||
{
|
||||
public TypeName : base(X, Y) { }
|
||||
}
|
||||
```
|
||||
|
||||
We mostly like both. The first syntax feels very simple and it effectively moves the "entire"
|
||||
constructor signature up to the type declaration, instead of just the parameter list. However,
|
||||
we don't think that class members would be in scope in the argument list for this base call
|
||||
and there are some rare cases where arguments to base calls may involve calls to static private
|
||||
helper methods in the class. Because of that we think the second syntax is more versatile and
|
||||
reflects the full spectrum of options available in classes today.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Both syntaxes are accepted. If prioritization is needed, the base specification on the primary
|
||||
constructor body is preferred.
|
95
meetings/2020/LDM-2020-05-04.md
Normal file
95
meetings/2020/LDM-2020-05-04.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# C# Language Design for May 4, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Design review feedback
|
||||
|
||||
## Discussion
|
||||
|
||||
We had a design review on 2020-04-29 to bring our latest designs to the full review team and get
|
||||
feedback. Today we went over the feedback and how it would affect our design.
|
||||
|
||||
### Final initializers
|
||||
|
||||
- Design review said it was very complicated, when do I use an initializer vs a constructor?
|
||||
|
||||
A possible fix would be to try to run initializers *before* constructors, instead of after. The
|
||||
main problem is that this is not where object initializers (using setters) run today. It would be
|
||||
very distasteful to have `init-only` setters run at a different time from regular setters, and
|
||||
worse to subtly run the setters at a different time just because of the presence of a different
|
||||
`init-only` field.
|
||||
|
||||
This is a difficult piece of feedback to reconcile, because it doesn't present a clear direction.
|
||||
However, we're not sure we need to finish the design for final initializers now. We still think
|
||||
the scenarios are useful, but there are many scenarios which don't rely on those semantics. One
|
||||
of the most important scenarios that we were worried about was how to copy a type that had
|
||||
private fields that should not be copied. One proposal was to write a final initializer which
|
||||
either resets certain fields, or `throw`s if the state is invalid. Our proposed alternative for
|
||||
this situation is to write your own copy constructor, which sets up the appropriate state for the
|
||||
copy.
|
||||
|
||||
However, final initializers do address a significant shortfall in existing scenarios, namely that
|
||||
there's no way to validate a whole object in a property setter (or initter). In that sense we do
|
||||
have many existing issues, separate from our records designs, which would be addressable with the
|
||||
feature. There is also no way to validate an object after a `with` expression since necessarily.
|
||||
|
||||
### Factory methods
|
||||
|
||||
The review team agreed about the necessity of "factory" semantics in the `with` expression, namely
|
||||
that the with expression essentially requires a `virtual` Clone method to work correctly through
|
||||
inheritance, but was not convinced that the feature was generally useful.
|
||||
|
||||
We're also not convinced that it's generally useful, but limiting `with` to only be usable on a
|
||||
record is a significant change from where we were before, where records are currently fully
|
||||
representable as regular classes.
|
||||
|
||||
We need to consider if we are willing to live with this limitation, or need a way of specifying
|
||||
the appropriate `Clone` method in source.
|
||||
|
||||
### Structs as records
|
||||
|
||||
Can every struct be a record automatically? We don't need a `Clone` method, because structs
|
||||
already copy themselves and they already implement value equality (albeit sometimes
|
||||
inefficiently). If we take this stance, would we want to explicitly design records as "struct
|
||||
behavior for classes?" If that's true, we would seek to use the behavior of structs as a template
|
||||
for records.
|
||||
|
||||
### Positional records
|
||||
|
||||
The feedback was negative about making a primary constructor parameters different from positional
|
||||
record parameters. The proposal during the design meeting was that primary constructors would see
|
||||
parameters as "captured" in the scope of the class, while records would generate public
|
||||
properties for each parameter. This is a big semantic divergence, as expressions like
|
||||
`this.parameter` would be legal in the body of a positional record, but illegal in the body of a
|
||||
class with a primary constructor. One way of shrinking the semantic gap would be to always
|
||||
generate members based on primary constructor parameters, but in regular classes those members
|
||||
would be private fields, while in records they would be public init-only properties. Even this
|
||||
semantic difference was perceived as too inconsistent.
|
||||
|
||||
We have two proposals to unify the behavior inside and outside of records. On one end, we could
|
||||
try to view primary constructors as a syntactic space to contain more elements. By default,
|
||||
primary constructors would be simple parameters, which could be closed over in the class body. By
|
||||
allowing member syntax in the parameter list, the user would have more control over the
|
||||
declaration. For instance,
|
||||
|
||||
```C#
|
||||
public class Person(
|
||||
public string Name { get; init; }
|
||||
);
|
||||
```
|
||||
|
||||
would generate a public property named `Name` instead of simply a parameter and the property
|
||||
would be implicitly assigned in the constructor.
|
||||
|
||||
On the other hand, we could _always_ make public properties, abandoning the idea of
|
||||
primary-constructor-parameters-as-closures. In this formulation,
|
||||
|
||||
```C#
|
||||
class C(int X, int Y);
|
||||
```
|
||||
|
||||
would generate two properties, X and Y. If this is made into a record e.g., `data class C(int X,
|
||||
int Y)`, then the same record members would be synthesized as in a nominal record.
|
||||
|
||||
We did not settle on a conclusion, but have a rough sense that having a primary constructor
|
||||
always generate properties is preferred.
|
79
meetings/2020/LDM-2020-05-06.md
Normal file
79
meetings/2020/LDM-2020-05-06.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
# C# LDM for May 6, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `if (e is not int i)`
|
||||
2. Target-typed conditional
|
||||
3. Extension GetEnumerator
|
||||
4. `args` in top-level programs
|
||||
|
||||
## Discussion
|
||||
|
||||
### `if (e is not int i)`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3369
|
||||
|
||||
There are broader features that we'd to consider here as well, for instance allowing
|
||||
some declarations below `or` patterns. However, this should be compatible with broader
|
||||
changes and is easy to implement right now.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted for C# 9. Further elaborations will be considered, assuming the schedule could
|
||||
accept it.
|
||||
|
||||
### Target-typed conditional
|
||||
|
||||
We still unfortunately have a breaking change here with
|
||||
|
||||
```C#
|
||||
M(b ? 1 : 2, 1); // calls M(long, long) without this feature; ambiguous without this feature
|
||||
|
||||
M(short, short);
|
||||
M(long, long);
|
||||
```
|
||||
|
||||
As always, breaking changes are very worrying, unless we are confident that almost no real-world
|
||||
code would be broken. If the breaking change results in an ambiguity instead of silent different
|
||||
codegen, that is substantially better, as people would at least know that the compiler changed
|
||||
behavior. At the moment, we only think that this change could result in new ambiguities, not
|
||||
different behavior.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll do some more investigation, try to find code that would be broken, and see if we can accept
|
||||
the change.
|
||||
|
||||
### Extension GetEnumerator
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/43147
|
||||
|
||||
Conclusions:
|
||||
|
||||
No objections to the proposals as written.
|
||||
|
||||
### `args` in Top-Level programs
|
||||
|
||||
If the top-level statements are logically inside a `Main` method, it would be very useful to have
|
||||
access to the command line arguments for the program. You can access these via
|
||||
`Environment.GetCommandLineArgs()`, but it's unfortunate that this is both different from the
|
||||
APIs in Main, and `Environment.GetCommandLineArgs()` includes the program name, and `args` in
|
||||
Main does not.
|
||||
|
||||
If we want to do something, we could have a magic variable named `args` (similar to `value` in
|
||||
setters) or a property in the framework called `Args` (e.g. `Environment.Args`).
|
||||
|
||||
In favor of the property, fewer language-level changes means fewer things that people have to
|
||||
learn.
|
||||
|
||||
In favor of the `args` magic variable, it's simpler to use than a property (since the property
|
||||
would either have to qualified with a type name, or a `using static` would have to be added) and
|
||||
a language feature for the inputs (command line args) mirrors the language feature for the output
|
||||
(returning an `int` that turns into the process exit code).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with the `args` magic variable. We still need to decide on the scope: either equivalent
|
||||
to top-level locals, which are visible in all files but inaccessible, or only in scope in
|
||||
top-level statements. If we make it visible in all files we would only add the variable if there
|
||||
is at least one top-level statement in the program.
|
107
meetings/2020/LDM-2020-05-11.md
Normal file
107
meetings/2020/LDM-2020-05-11.md
Normal file
|
@ -0,0 +1,107 @@
|
|||
|
||||
# C# Language Design Meeting for May 11, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
## Discussion
|
||||
|
||||
Today we tried to resolve some of the biggest questions about records,
|
||||
namely how to unify the semantics of nominal records and positional
|
||||
records, and what are the key scenarios that we are trying to resolve
|
||||
with records.
|
||||
|
||||
The main inconsistency is that the members `record class Person(string FirstName, string
|
||||
LastName)` are very different from the members in `class Person(string firstName, string
|
||||
lastName)`. One way of resolving this is to unify the meaning of the declaration in the direction
|
||||
of primary constructors. In this variant, the parameters of a primary constructor always capture
|
||||
items by default.
|
||||
|
||||
To produce public init-only properties like we were exploring, we would require an extra
|
||||
keyword, `data`, that could be generalizable. So a record which has two public init-only
|
||||
members would be written
|
||||
|
||||
```C#
|
||||
record Person(data string FirstName, data string LastName);
|
||||
```
|
||||
|
||||
This would allow a generalizable `data` keyword that could be applied even in regular
|
||||
classes, e.g.
|
||||
|
||||
```C#
|
||||
class Person
|
||||
{
|
||||
data string FirstName;
|
||||
data string LastName;
|
||||
public Person(string first, string last)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The worry here is that we're harming an essential motivation for records, namely a short syntax
|
||||
for immutable data types. In the above syntax, `record` alone does not mean immutable data type,
|
||||
but instead only value equality and non-destructive mutation. A problem with this is that value
|
||||
equality is dangerous for mutable classes, since the hash code can change after being added to a
|
||||
dictionary. This was why we were previously cautious about adding a general feature for value
|
||||
equality. One option to discourage misuse would be to provide a warning for any non-immutable
|
||||
member in a record class.
|
||||
|
||||
The other problem is, frankly, it's not that short. Aside from some duplication of intent by
|
||||
requiring both `record` and `data` modifiers, it also requires applying the `data` modifier to
|
||||
each member, so the overhead grows larger as the type does.
|
||||
|
||||
Alternatively, we could go in the complete opposite direction: limit customizability by making
|
||||
records all about public immutable data.
|
||||
|
||||
For instance, nominal records could also have syntax abbreviation
|
||||
|
||||
```C#
|
||||
record Person { string FirstName; string LastName; }
|
||||
```
|
||||
|
||||
and we could avoid confusion by prohibiting other members entirely.
|
||||
|
||||
This would look at lot more like positional records, e.g.
|
||||
|
||||
```C#
|
||||
record Person(string FirstName, string LastName);
|
||||
```
|
||||
|
||||
and we could introduce further restrictions on those by also disallowing other members
|
||||
in the body, or even disallowing primary constructors entirely.
|
||||
|
||||
Disallowing all members inside of records is draconian, but not entirely without precedence.
|
||||
Enums work the same way in C# and members are added via extensions methods. That's not a ringing
|
||||
endorsement since we've considered proposals for allowing members in enums before, but it also
|
||||
doesn't put it outside the realm of possibility for C#, especially in earlier forms.
|
||||
|
||||
The main drawback of the simplest form is the risk that we might have trouble evolving the
|
||||
feature to fit all circumstances. If we wanted to allow a user to define private fields, the
|
||||
syntax with no accessibility modifier now means "public init-only property" so we might not
|
||||
be able to add support for private fields at all, or we might have to use a syntactic distinction
|
||||
that requires a `private` accessibility, which is a subtle change.
|
||||
|
||||
### Conclusion
|
||||
|
||||
We largely prefer the short syntax for records. A nominal record would look like
|
||||
|
||||
```C#
|
||||
record class Person { string FirstName; string LastName; }
|
||||
```
|
||||
|
||||
This would create a class with public `init-only` properties named `FirstName`
|
||||
and `LastName`, along with equality and non-destructive mutation methods.
|
||||
|
||||
Similarly,
|
||||
|
||||
```C#
|
||||
record class Person(string FirstName, string LastName);
|
||||
```
|
||||
|
||||
would create a class with all of the above, but also a constructor and Deconstruct.
|
||||
|
||||
We have yet to confirm whether `record` disallows private fields entirely, or if it
|
||||
just changes the default accessibility.
|
147
meetings/2020/LDM-2020-05-27.md
Normal file
147
meetings/2020/LDM-2020-05-27.md
Normal file
|
@ -0,0 +1,147 @@
|
|||
|
||||
# C# Language Design Meeting for May 27, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Records -- syntax
|
||||
|
||||
## Discussion
|
||||
|
||||
### Syntax Questions
|
||||
|
||||
We got significant feedback that `record` is a better name than `data` for indicating
|
||||
a `record`. There are two syntaxes we've been considering here:
|
||||
|
||||
1. `record class Person { ... }`
|
||||
2. `record Person { ... }`
|
||||
|
||||
The main difference here is that (2) has less obvious space for a `struct` token, which
|
||||
raises the question of whether "record structs" are a feature we want to enable.
|
||||
|
||||
There are a couple arguments for why records would be useful for structs. The first is
|
||||
that "value behavior" is a general feature that could be useful for both structs and classes.
|
||||
Value equality exists for structs today, but it is potentially slow in the runtime implementation.
|
||||
|
||||
The second is that the syntax provided for classes is also useful for structs. The positional
|
||||
syntax specifically seems attractive because it has a lot of similarity to tuples and allows a
|
||||
form of "named tuple."
|
||||
|
||||
```C#
|
||||
record struct Point(int X, int Y);
|
||||
```
|
||||
|
||||
On the other hand, we could improve the performance of equality, completely separate from records.
|
||||
For instance, the compiler could add equality methods if they are not present. We also do not necessarily
|
||||
need to address structs first. Since structs already have many features of records they are, in a sense,
|
||||
"less far behind" than classes in record features. It makes sense to concentrate first on classes and
|
||||
consider augmentations for structs in a future update.
|
||||
|
||||
So to return to the original question, we have to decide if we want to move forward with option (2), which
|
||||
is a new form of declaration. Notably, this is a breaking change for certain scenarios e.g.,
|
||||
|
||||
```C#
|
||||
record M(int X, int Y) { ... }
|
||||
|
||||
class C
|
||||
{
|
||||
record M(int X, int Y) { ... }
|
||||
partial record M(int X, int Y);
|
||||
}
|
||||
```
|
||||
|
||||
All of these are currently method declaration syntax. In C# 9 this would be a record declaration
|
||||
with a positional constructor and a body. Normally we would never consider this kind of change,
|
||||
but since we started shipping with .NET Core we do not automatically upgrade language version
|
||||
unless the target framework is the newest one (i.e., NET 5).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Do not support structs for now. They already support many features of records and we can add
|
||||
more, time permitting.
|
||||
|
||||
The accepted proposal is that the syntax, `<modifiers> <attributes> 'record'` followed by
|
||||
`identifier` and either '(', '{', or '<' would be contextually parsed as a record declaration
|
||||
only if the language version is C# 9.
|
||||
|
||||
### Short-property syntax
|
||||
|
||||
We previously agreed that, to unify the syntax forms in the positional and nominal declaration, we
|
||||
would allow fields in nominal records with no modifiers to instead be interpreted as public auto-properties.
|
||||
After looking at feedback and exploring some of the related issues, we've decided that's not the best approach.
|
||||
|
||||
There are a few proposals on the table:
|
||||
|
||||
1. Leave positional records the same, do not provide special syntax for nominal records.
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
2. Unify the declaration forms in favor of nominal records, allowing property declarations in the
|
||||
record parameter list
|
||||
|
||||
```C#
|
||||
public record Point(
|
||||
public int X { get; init; },
|
||||
public int Y { get; init; }
|
||||
)
|
||||
public record Point
|
||||
{
|
||||
public int X { get; init; },
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
3. Keep positional records the same, provide a new modifier (e.g., `data` or `init`) for members
|
||||
which means "public init-only property"
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
||||
4. Provide the new modifier from (3), and require it in both types of records
|
||||
|
||||
```C#
|
||||
public record Point(data int X, data int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
||||
After discussion, we prefer (3). Positional records already seem to have enough syntactic distinction and
|
||||
the `data` keywords seem superfluous in this position. It also makes the shortest syntax form match up
|
||||
with the most common use case.
|
||||
|
||||
However, we do think some further keyword is necessary for nominal records. Looking too much like existing
|
||||
fields seems like it would be too confusing, especially if we want to also allow fields in records.
|
||||
|
||||
Instead, we're considering leaving positional records to generate public auto-properties by default, partly
|
||||
because they are already a significantly different syntax that cannot be confused with existing language
|
||||
constructs, and providing a new mechanism for positional records.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Keep positional records the same, provide a `data` modifier for fields which means "public
|
||||
init-only property"
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
163
meetings/2020/LDM-2020-06-01.md
Normal file
163
meetings/2020/LDM-2020-06-01.md
Normal file
|
@ -0,0 +1,163 @@
|
|||
|
||||
# C# Language Design for June 1, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records:
|
||||
|
||||
1. Base call syntax
|
||||
2. Synthesizing positional record members and assignments
|
||||
3. Record equality through inheritance
|
||||
|
||||
## Discussion
|
||||
|
||||
### Record base call syntax
|
||||
|
||||
We'd like to reconsider adding a base call syntax to a record declaration, i.e.
|
||||
|
||||
```antlr
|
||||
record_base
|
||||
: ':' class_type argument_list?
|
||||
| ':' interface_type_list
|
||||
| ':' class_type argument_list? interface_type_list
|
||||
;
|
||||
```
|
||||
|
||||
The main question is how the scoping of the parameters from the record positional
|
||||
constructor interact with the base call syntax and the record body.
|
||||
|
||||
We would definitely like the parameters to be in scope inside the base call. For the record body,
|
||||
it's proposed that the parameters of the primary constructor are in scope for initializers, and
|
||||
the primary constructor body (if we later accept a proposal for such syntax). The parameters
|
||||
shadow any members of the same name. The parameters are not in scope outside of these locations.
|
||||
|
||||
To unify the scoping behavior between the base call and the body, we propose that members of the
|
||||
body are also in scope in the base call syntax. Instance members would be an error in these locations
|
||||
(similar to how instance members are in scope in initializers today, but an error to use), but
|
||||
the parameters of the positional record constructor would be in scope and useable. Static members
|
||||
would also be useable, similar to how base calls work in ordinary constructors today.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The above proposals are accepted.
|
||||
|
||||
### Synthesized positional record members
|
||||
|
||||
A follow-up question is how to do generation for auto-generated positional properties. We need
|
||||
to decide both 1) when we want to synthesize positional members and 2) when we want to initialize
|
||||
the corresponding members. The affect is most clearly visible in the example below, where the
|
||||
initialization order will affect what values are visible at various times during construction,
|
||||
namely whether the synthesized properties are initialized before or after the `base` call.
|
||||
|
||||
```C#
|
||||
record Person(string FirstName, string LastName)
|
||||
{
|
||||
public string Fullname => $"{FirstName} {LastName}";
|
||||
public override string ToString() => $"{FirstName} {LastName}";
|
||||
}
|
||||
|
||||
record Student(string FirstName, string LastName, int Id)
|
||||
: Person(FirstName, LastName)
|
||||
{
|
||||
public override string ToString() => $"{FirstName} {LastName} ({ID})";
|
||||
}
|
||||
```
|
||||
|
||||
First we discussed when to synthesize members, namely when an "existing" member will prevent
|
||||
synthesis. A simple rule is that we synthesize members when there is no accessible, concrete
|
||||
(non-abstract) matching member in the type already, either because it was inherited or because it
|
||||
was declared. The rule for matching is that if the member would be considered identical in signature,
|
||||
or if it would require the `new` keyword in an inheritance scenario, those members would "match." This
|
||||
rule allows us to avoid generating duplicate members for record-record inheritance and also produces the
|
||||
intuition that we should err on the side of not synthesizing members when they could be confused with
|
||||
an existing member.
|
||||
|
||||
Second, we discussed when and in what order assignments were synthesized from positional record
|
||||
parameters to "matching" members. A starting principle is that in record-record inheritance we don't
|
||||
want to duplicate assignment -- the base record will already assign its members. In that case, we could
|
||||
choose to assign only members synthesized or declared in the current record. That would mean
|
||||
|
||||
```C#
|
||||
record R(int X)
|
||||
{
|
||||
public int X { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
would initialize the `X` property to the value of the constructor parameter even though the property
|
||||
is not compiler synthesized. However, we would have to decide if it is synthesized before or after
|
||||
the `base` call. In essence, the question is how we de-sugar the assignments. Is `record Point(int X, int Y);`
|
||||
equivalent to
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y) : Base
|
||||
{
|
||||
public int X { get; init; } = X;
|
||||
public int Y { get; init; } = Y;
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y) : Base
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
public Point
|
||||
{
|
||||
this.X = X;
|
||||
this.Y = Y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that today property and field initializers are always executed before the `base` call, while
|
||||
statements in the constructor body are executed afterwards and we are disinclined to change that
|
||||
for record initializers.
|
||||
|
||||
Looking at the examples as a whole, we think using the initializer behavior is good -- it's easy
|
||||
to understand and more likely to be correct in the presence of a virtual call in the base class,
|
||||
but it makes things significantly more complicated if we synthesize it even for user-written
|
||||
properties. Is the initializer synthesized even if there's already an initializer on the property?
|
||||
What if the user-written property isn't an auto-property?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think it's much clearer if we simplify the rules to only initialize synthesized properties.
|
||||
Effectively, if you replace the synthesized record property, you also have to write the initialization,
|
||||
if you want it. In the case that the property is not already declared, e.g. `record Point(int X, int Y);`
|
||||
the equivalent code is
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y)
|
||||
{
|
||||
public int X { get; init; } = X;
|
||||
public int Y { get; init; } = Y;
|
||||
}
|
||||
```
|
||||
|
||||
### Equality through inheritance
|
||||
|
||||
We have a number of small and large questions about how records work with inheritance.
|
||||
|
||||
Q: What should we do if one of the members which we intend to override, like object.Equals and
|
||||
object.GetHashCode, are sealed?
|
||||
|
||||
A: Error. This is effectively outside of the scope of automatic generation.
|
||||
|
||||
Q: Should we generate a strongly-typed Equals (i.e., `bool Equals(T)`) for each record declaration? What
|
||||
about implementing `IEquatable<T>`?
|
||||
|
||||
A: Yes. Implementing `IEquatable<T>` is very useful and would require a strongly-typed equals method. We
|
||||
could explicitly implement the method, but we also think this is useful surface area. If we broaden
|
||||
support to structs, this would prevent a boxing conversion, which has a significant performance impact.
|
||||
Even for classes this could avoid extra type checks and dispatch.
|
||||
|
||||
Q: Should each record declaration re-implement equality from scratch? Or should we attempt to dispatch
|
||||
to base implementations of equality?
|
||||
|
||||
A: For the first record in a type hierarchy, we should define equality based on all the accessible fields,
|
||||
including inherited ones, in the record. For records inheriting from a class with an existing
|
||||
`EqualityContract`, we should assume that it implements our contract appropriately, and delegate comparing
|
||||
the EqualityContract itself and the base fields to the base class.
|
41
meetings/2020/LDM-2020-06-10.md
Normal file
41
meetings/2020/LDM-2020-06-10.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
# C# LDM for June 10, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. "Roles"
|
||||
|
||||
## Discussion
|
||||
|
||||
Exploration of previous proposal: #1711
|
||||
|
||||
This is a topic that we've explored before which we're reviving for further consideration and discussion.
|
||||
|
||||
We have a "role" proposal, but it's more of a starting point for a final design. There are a number of
|
||||
different problems we can presumably solve here, but it seems like we have some intersecting features that
|
||||
might address multiple problems simultaneously.
|
||||
|
||||
There are many tradeoffs to consider in these designs. One of the most well-known is sometimes called
|
||||
"incoherence," where the ability to implement an interface in two ways on the same type effectively causes
|
||||
the two implementations to "cross" each other in ways that can be hard to predict. For instance, if two
|
||||
people implemented `IEquatable<T>` on the same third-party type, and both added it to a dictionary, if they
|
||||
used different `GetHashCode` implementations then the same member could be added twice, and each consumer
|
||||
wouldn't see the implementation used by other consumers.
|
||||
|
||||
Another tradeoff is the ability to use the role as a type, namely refer to it in a type position. This
|
||||
is often desirable, but has some tradeoffs in type equivalence (see SML modules for alternative notions
|
||||
of type equivalence through functors).
|
||||
|
||||
The Roles proposal as a whole seems very powerful, but there are many big questions here. The biggest,
|
||||
most pressing question is: what problems do we think are the most important and how big a feature do
|
||||
we need to address them? Providing a way to abstract over different numeric abstractions is a concrete
|
||||
scenario, but it may not need the fully generalized mechanism. Allowing existing types to conform
|
||||
to an abstract after definition is also powerful and has many possible use cases, but how flexible
|
||||
do we need to make that mechanism? Can it only be used in generics? Can you implement abstractions
|
||||
defined in other compilations on types defined in other compilations?
|
||||
|
||||
The performance concerns are also very real. We have a few mechanisms for abstraction in the language
|
||||
today, but a lot of those mechanisms come with performance costs like allocation that make them
|
||||
unusable in performance-sensitive scenarios. We would like more zero-cost abstractions if possible,
|
||||
but we're not sure what functionality we could provide in those circumstances and whether the features
|
||||
would fit well into the existing ecosystem.
|
197
meetings/2020/LDM-2020-06-15.md
Normal file
197
meetings/2020/LDM-2020-06-15.md
Normal file
|
@ -0,0 +1,197 @@
|
|||
|
||||
# C# Language Design Meeting for June 15, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Discussion
|
||||
|
||||
### `modreq` for init accessors
|
||||
|
||||
We've confirmed that the modreq design for `init` accessors:
|
||||
|
||||
- The modreq type `IsExternalInit` will be present in .NET 5.0, and will be recognized if
|
||||
defined in source
|
||||
|
||||
- The feature will only be fully supported in .NET 5.0
|
||||
|
||||
- Usage of the property (including the getter) will not be possible on older compilers, but
|
||||
if the compiler is upgraded (even if an older framework is being used), the getter will be
|
||||
usable
|
||||
|
||||
### Initializing `readonly` fields in same type
|
||||
|
||||
We previously removed `init` fields from the design proposal because we didn't think it was
|
||||
necessary for the records feature and because we didn't want to allow fields which were
|
||||
declared `readonly` before records to suddenly be settable externally in C# 9, contrary to
|
||||
the author's intent.
|
||||
|
||||
One extension would be to allow `readonly` fields to be set in an object initializer only inside
|
||||
the type. In this case you could still use object initializers to set readonly fields in
|
||||
static factories, but because they would be a part of your type you would always know the intent
|
||||
of the `readonly` modifier. For instance,
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public readonly string? ReadonlyField;
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyField = null; };
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, we may not need a new feature for many of these scenarios. An init-only
|
||||
property with a private `init` accessor behaves similarly.
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public string? ReadonlyProp { get; private init; }
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyProp = null; };
|
||||
}
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We still think `readonly` fields are interesting, but we're not sure of the scenarios yet.
|
||||
Let's keep this on the table, but leave it for a later design meeting.
|
||||
|
||||
### `init` methods
|
||||
|
||||
We previously considered having `init` methods which could modify `readonly` members just
|
||||
like `init` accessors. This could enable scenarios like "immutable collection initializers",
|
||||
where members can be added via an `init` Add method.
|
||||
|
||||
The problem is that the vast majority of immutable collections in the framework today would be
|
||||
unable to adopt this pattern. Collection initializers are hardcoded to use the name `Add` today
|
||||
and almost all the immutable collections already have `Add` methods that are meant for a different
|
||||
purpose -- they return a copy of the collection with the added item.
|
||||
|
||||
If we naively extend `init` to collection initializers most immutable collections wouldn't be able
|
||||
to adopt them because they couldn't make their `Add` methods `init`-only, and no other method name
|
||||
is allowed in a collection initializer. In addition, some types, like ImmutableArrays, would be
|
||||
forced to implement init-only collection initializers very inefficiently, by declaring a new array
|
||||
and copying each item every time `Add` is called.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're still very interested in the feature, but we need to determine how we can add it in a way
|
||||
that provides a path forward for our existing collections.
|
||||
|
||||
### Equality dispatch
|
||||
|
||||
We have an equality implementation that we think is functional, but we're not sure it's the most
|
||||
efficient implementation. Consider the following chain of types:
|
||||
|
||||
```C#
|
||||
class R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R1);
|
||||
public virtual bool Equals(R1 other)
|
||||
=> !(other is null) &&
|
||||
this.EqualityContract == other.EqualityContract
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R2 : R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R3 : R2
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R2 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
```
|
||||
|
||||
The benefit of the above strategy is that each virtual call goes directly
|
||||
to the appropriate implementation method for the runtime type. The drawback
|
||||
is that we're effectively generating a quadratic number of methods and overrides
|
||||
based on the number of derived records.
|
||||
|
||||
One alternative is that we could not override the Equals methods of anything
|
||||
except our `base`. This would cause more virtual calls to reach the implementation,
|
||||
but reduce the number of overrides.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We need to do a deep dive on this issue and explore all the scenarios. We'll come
|
||||
back once we've outlined all the options and come up with a recommendation.
|
||||
|
||||
### Affirming some previous decisions
|
||||
|
||||
Proposals for copy constructors
|
||||
|
||||
- Do not include initializers (including for user-written copy constructors)
|
||||
|
||||
- Require delegation to a base copy constructor or `object` constructor
|
||||
|
||||
- If the implementation is synthesized, this behavior is synthesized
|
||||
|
||||
Proposal for Deconstruct:
|
||||
|
||||
- Doesn't delegate to a base Deconstruct method
|
||||
|
||||
- Synthesized body is equivalent to a sequence of assignments of member
|
||||
accesses. If any of these assignments would be an error, an error is produced.
|
||||
|
||||
It's also proposed that any members which are either dispatched to in a derived record
|
||||
or expected to be overridden in a derived record will produce an error for synthesized
|
||||
implementations if the required base member is not found. This includes if the base
|
||||
member was not present in the immediate base, but was inherited instead. For some situations
|
||||
this may mean that the user can write a substituted implementation for that synthesized
|
||||
member, but for the copy constructor this effectively forbids record inheritance, since
|
||||
the valid base member must be present even in a user-defined implementation.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
All of the above decisions are upheld.
|
||||
|
||||
### Non-generic IEnumerable
|
||||
|
||||
Currently in the framework `IEnumerable.Current` (the non-generic interface) is annotated to
|
||||
return `object?`. This produces a lot of warnings in legacy code that `foreach` over the result
|
||||
with types like `string`, which is non-nullable. We have two proposals to resolve this:
|
||||
|
||||
- Un-annotate `IEnumerable.Current`. This will keep the member nullable-oblivious and no warnings
|
||||
will be generated, even if the property is called directly
|
||||
|
||||
- Special-case the compiler behavior for `foreach` on `IEnumerable` to suppress nullable warnings
|
||||
when calling `IEnumerable.Current`
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Overall, we prefer un-annotation. Since this interface is essentially legacy, we feel that
|
||||
providing nullable analysis is potentially harmful and rarely beneficial.
|
122
meetings/2020/LDM-2020-06-17.md
Normal file
122
meetings/2020/LDM-2020-06-17.md
Normal file
|
@ -0,0 +1,122 @@
|
|||
|
||||
# C# Language Design Meeting for June 17, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Null-suppression & null-conditional operator
|
||||
1. `parameter!` syntax
|
||||
1. `T??`
|
||||
|
||||
## Discussion
|
||||
|
||||
### Null-suppression & null-conditional operator
|
||||
|
||||
Issue #3393
|
||||
|
||||
We generally agree that this is uninintended and unfortunate, essentially a spec bug. The
|
||||
only question is whether to allow `!` at the end of a `?.`, as well as in the "middle". In
|
||||
some sense
|
||||
|
||||
### `parameter!` syntax
|
||||
|
||||
This has been delayed because we haven't been able to agree on the syntax. The main contenders
|
||||
are
|
||||
|
||||
```C#
|
||||
void M(string param!)
|
||||
void M(string param!!)
|
||||
void M(string! param)
|
||||
void M(string !param)
|
||||
void M(checked string param)
|
||||
void M(string param ?? throw)
|
||||
void M(string param is not null)
|
||||
void M(notnull string param)
|
||||
void M(null checked string param)
|
||||
void M(bikeshed string param)
|
||||
void M([NullChecked("Helper")] string param)
|
||||
/* contract precondition forms */
|
||||
void M(string param) Requires.NotNull(param)
|
||||
void M(string param) when param is not null
|
||||
```
|
||||
|
||||
The simplest form, `void M(string param!)` is attractive, but looks very similar to the null
|
||||
suppression operator. The biggest problem is that you can see them as having very different
|
||||
meanings -- `!` in an expression silences nullable warnings, while `!` on a parameter does the
|
||||
opposite, it actually produces an exception on nulls. However, the result of both forms is a
|
||||
value which is treated by the compiler as not-null, so there is a way of seeing them as similar.
|
||||
|
||||
Moving it to the other side of the parameter name, `!param`, would resolve some of the similarity
|
||||
with the null suppression operator, but it also looks a lot like the `not` prefix operator. There's
|
||||
slightly less contradiction in these operators, but it still features a bit of syntactic overloading.
|
||||
|
||||
`string!` has a couple problems, including a suggestion that it's a part of the type (which it would not
|
||||
be), and that sometimes you may want to use the operator on nullable types, like `AssertNotNull` methods.
|
||||
It also wouldn't be usable in simple lambdas without types.
|
||||
|
||||
`checked` suggests integer over/underflow more than nullability.
|
||||
|
||||
`param!!` has some usefulness that we could provide a corresponding expression form -- `!`
|
||||
suppresses null warnings, while `!!` actually checks for null and throws if it is found. On the
|
||||
other hand, it also reads a bit strangely, especially since we're adding a new syntax form
|
||||
instead of trying to reuse some forms we already have. On the other hand, the fact that it's
|
||||
different enough to look different, while also short enough to be used commonly has a lot in
|
||||
favor of it. In general we historically have a bias towards making new things strongly
|
||||
distinguished from existing code, but shortly after introducing the feature we tend to wish that
|
||||
things were less verbose and didn't draw as much attention in the code. On the other hand, the
|
||||
nullable feature has a rule that it should not affect code semantics, while the purpose of this
|
||||
feature is to affect code semantics. `param!!` could be seen as being too similar to other things
|
||||
in the nullable feature, but to some people it also stands out because of the multiple operators.
|
||||
|
||||
We did a brief ranked choice vote and came up with the following ranking, not as definitive, just to
|
||||
measure our current preferences:
|
||||
|
||||
1. `void M(string param!!)`
|
||||
2. `void M(nullcheck string param)`
|
||||
3. `void M([NullChecked(Helper)] string param)`
|
||||
|
||||
Many people don't have strong opinions, so we don't have a clear winner coming out.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're getting closer to consensus, but we need to discuss this more and consider some of the
|
||||
long-term consequences, as well as impact on other pieces of the language design.
|
||||
|
||||
### `T??`
|
||||
|
||||
Unfortunately there are multiple parsing ambiguities with `T??`:
|
||||
|
||||
```C#
|
||||
(X??, Y?? y) t;
|
||||
using (T?? t = u) { }
|
||||
F((T?? t) => t);
|
||||
```
|
||||
|
||||
This has left us looking back to the original syntax: `T?`. The original reason we rejected this
|
||||
was that there could be confusion that `T?` means "maybe default," so that the type is nullable
|
||||
if it's a reference type, but not nullable if it's a value type.
|
||||
|
||||
If we want to allow the `T?` syntax anyway, we need some syntax to specify that, for overrides,
|
||||
we want the method with no constraints (unconstrained). This is because constraints cannot
|
||||
generally be specified in overrides or explicit implementations, so previously `T?` always meant
|
||||
`Nullable<T>`, but now it may not. We added the `class` constraint for nullable reference types,
|
||||
but neither `T : class` nor `T : struct` help if the `T` is unconstrained. In essence, we need a
|
||||
constraint that means unconstrained. Some options include:
|
||||
|
||||
```C#
|
||||
override void M1<[Unconstrained]T,U>(T? x) // a
|
||||
override void M1<T,U>(T? x) where T: object? // b
|
||||
override void M1<T,U>(T? x) where T: unconstrained // c
|
||||
override void M1<T,U>(T? x) where T: // d
|
||||
override void M1<T,U>(T? x) where T: ? // e
|
||||
override void M1<T,U>(T? x) where T: null // f
|
||||
override void M1<T,U>(T? x) where T: class|struct // g
|
||||
override void M1<T,U>(T? x) where T: class or struct // h
|
||||
override void M1<T,U>(T? x) where T: cluct // joke
|
||||
override void M1<T,U>(T? x) where T: default // i
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The `default` constraint seems most reasonable. It would only be allowed in overrides and
|
||||
explicit interface implementations, purely for the purpose of differentiating which method
|
||||
is being overridden or implemented. Let's see if there are other problems.
|
104
meetings/2020/LDM-2020-06-22.md
Normal file
104
meetings/2020/LDM-2020-06-22.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
|
||||
# C# Language Design Meeting for June 22, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Data properties
|
||||
|
||||
1. Clarifying what's supported in records for C# 9
|
||||
|
||||
- Structs
|
||||
|
||||
- Inheritance with records and classes
|
||||
|
||||
## Discussion
|
||||
|
||||
### `data` properties
|
||||
|
||||
We've been working on an implementation for data properties and have some questions about
|
||||
modifiers, which are currently not allowed.
|
||||
|
||||
There are some modifiers that could be legal, like `new`, `abstract`, `virtual`, and `override`.
|
||||
Some of these, especially `new` and `override` would probably prevent `data` properties from
|
||||
being used at all, since a warning would be generated if there's a matching member in the base
|
||||
that requires either `override` or `new`.
|
||||
|
||||
However, the point of data properties was to be brief. Adding modifiers would potentially cloud
|
||||
the purpose of the feature. The syntactical abbreviation isn't strictly required because the
|
||||
equivalent property can always be written explicitly:
|
||||
|
||||
```C#
|
||||
data int X;
|
||||
|
||||
// versus
|
||||
|
||||
public int X { get; init; }
|
||||
```
|
||||
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Based on where we are in the schedule for C# 9, we probably will not have time to respond to feedback.
|
||||
Therefore, we're planning on putting this feature into preview and not shipping it with C# 9. For now,
|
||||
let's allow the following modifiers: `public`, `new`, `abstract`, `virtual`, and `override`. This will
|
||||
provide an option for allowing `data` properties to interact more smoothly with other constructs in C#,
|
||||
while maintaining some of the simplicity. We'll see how it feels in prototypes to gauge whether we went
|
||||
too far, or not far enough.
|
||||
|
||||
### Struct records
|
||||
|
||||
We're not sure how much space we have for structs in C# 9, but we do think structs are an
|
||||
interesting design point. We previously proposed that structs could support a variety of record
|
||||
behaviors without being a record. One idea would be to automatically implement `IEquatable<T>`
|
||||
for all structs, but this has a lot of potential negative impact for the runtime and the
|
||||
ecosystem: effectively every struct would add a large number of members which could seriously
|
||||
bloat metadata.
|
||||
|
||||
An alternative would be to work with the runtime to try to provide a runtime-generated form of
|
||||
`IEquatable<T>`. If it's cheap enough, we may be able to provide `IEquatable<T>` for structs as
|
||||
a as-necessary addition to all structs. However, this is a potentially very large change. Even
|
||||
small costs could add up, and even small semantic changes could impact someone.
|
||||
|
||||
Overall, the modification of arbitrary struct semantics just seems too risky. Something as simple
|
||||
as testing a struct for an implementation of `IEquatable<T>` could be a breaking change, and with
|
||||
a change this big, it's almost certain to be breaking for someone.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Altering the semantics of all structs as-is is out. We're leaning towards a real "record struct"
|
||||
declaration.
|
||||
|
||||
### Inheritance between records and classes
|
||||
|
||||
We currently have a restriction that records cannot inherit from classes and classes cannot
|
||||
inherit from records. We should confirm that this restriction is acceptable for C# 9.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We definitely see scenarios for expanding this, but we think the restriction is fine for the
|
||||
first version of records. Based on feedback and example scenarios, we can reconsider these
|
||||
restrictions and also the expected semantics.
|
||||
|
||||
### `with` expressions on non-records
|
||||
|
||||
This encompasses:
|
||||
|
||||
1. Finding some way to define a compatible `Clone` for user-defined classes
|
||||
|
||||
1. Anonymous types
|
||||
|
||||
1. Tuples
|
||||
|
||||
Compatibility for `Clone` in user-defined classes is the most difficult of these items because it
|
||||
relies on a new language feature for requiring that a new object is returned from the Clone
|
||||
method.
|
||||
|
||||
Anonymous types are very simple, we can retroactively define them as record types.
|
||||
|
||||
Tuples would be simple to special-case in the language, but it's probably more consistent to
|
||||
decide the semantics for struct records first, and then update the definition of
|
||||
System.ValueTuple to support them.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Anonymous types can be updated at any time, everything else should wait until after C# 9.
|
188
meetings/2020/LDM-2020-06-24.md
Normal file
188
meetings/2020/LDM-2020-06-24.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
# C# Language Design Meeting for June 24th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Confirming Function Pointer Decisions](#Confirming-Function-Pointer-Decisions)
|
||||
1. [Parameter Null Checking](#Parameter-Null-Checking)
|
||||
1. [Interface `static` Member Variance](#Interface-`static`-Member-Variance)
|
||||
1. [Property Enhancements](#Property-Enhancements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"It feels a little bit like we're playing code golf here"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Confirming Function Pointer Decisions
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516
|
||||
|
||||
There are a few open questions from a previous [LDM](LDM-2020-04-01.md) and a followup email chain
|
||||
that need to be confirmed before they can be implemented. These questions center around calling
|
||||
convention type lookup and how identifiers need to be written in source. The grammar we had roughly
|
||||
proposed after the previous meeting is:
|
||||
|
||||
```antlr
|
||||
func_ptr_calling_convention
|
||||
: 'managed'
|
||||
| 'unmanaged' ('[' func_ptr_callkind ']')?
|
||||
|
||||
func_ptr_callkind
|
||||
: 'CallConvCdecl'
|
||||
| 'CallConvStdcall'
|
||||
| 'CallConvThiscall'
|
||||
| 'CallConvFastcall'
|
||||
| identifier (',' identifier)*
|
||||
```
|
||||
|
||||
##### Calling Convention Lookup
|
||||
|
||||
When attempting to bind the `identifier` used in an unmanaged calling convention, should this follow
|
||||
standard lookup rules, such that the type must be in scope at the current location, or is using a
|
||||
form of special lookup that disregards the types in scope at the current location? The types valid
|
||||
in this location are a very specific set: they must come from the `System.Runtime.CompilerServices`
|
||||
namespace, and the types must have been defined in the same assembly that defines `System.Object`,
|
||||
regardless of the binding strategy used here, so it's really a question of whether the user has to
|
||||
include this namespace in their current scope, adding a bunch of types that they are generally not
|
||||
advised to use directly, and whether they can get an error because they defined their own calling
|
||||
convention.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Given the specificness required here, we will use special name lookup.
|
||||
|
||||
##### Required identifiers
|
||||
|
||||
The previous LDM did not specify the required syntax for the identifiers quite explicitly enough for
|
||||
implementation, and specified that identifiers should be lowercase while also having upper case
|
||||
identifiers in some later examples. The following rules are proposed as the steps the compiler will
|
||||
take to match the identifier to a type:
|
||||
|
||||
1. Prepend `CallConv` onto the identifier. No casemapping is performed.
|
||||
2. Perform special name lookup with that typename in the `System.Runtime.CompilerServices` namespace
|
||||
only considering types that are defined in the core library of the program (the library that defines
|
||||
`System.Object` and has no dependencies itself).
|
||||
|
||||
We also reconsidered the decision from the previous LDM on using lowercase mapping for the identifier
|
||||
names. There is convention for this in other languages: C/C++, for example, use `__cdecl` or similar
|
||||
as their calling convention specifiers, and given that this feature will be used for native interop
|
||||
with libraries doing this it would be nice to have some parity. However, this would introduce several
|
||||
issues with name lookup: existing special name lookup allows us to modify the `identifier` specified
|
||||
in source, but it does not allow us to modify the names of the types we're matching against, which
|
||||
we would need to do here. There is certainly an algorithm that could be specified here, but we overall
|
||||
felt that this was too complicated for what was a split aesthetic preference among members.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
The proposed rules are accepted. As a consquence, the identifier specified in source cannot start
|
||||
with `CallConv` in the name, unless the runtime were to add a type like `CallConvCallConv`.
|
||||
|
||||
### Parameter Null Checking
|
||||
|
||||
We ended the [previous meeting](LDM-2020-06-17.md) on this with two broad camps: support for the `!!`
|
||||
syntax, and support for some kind of keyword. Email discussion over the remainder of the week and
|
||||
polling showed that a clear majority supported the `!!` syntax.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will be moving forward with `!!` as the syntax for parameter null checking:
|
||||
|
||||
```cs
|
||||
public void M(Chitty chitty!!)
|
||||
```
|
||||
|
||||
### Interface `static` Member Variance
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3275
|
||||
|
||||
We considered variance in `static` interface members. Today, for co/contravariant type parameters
|
||||
used in these members, they must follow the full standard rules of variance, leading to some
|
||||
inconsistency with the way that `static` fields are treated vs `static` properties or methods:
|
||||
|
||||
```cs
|
||||
public interface I<out T>
|
||||
{
|
||||
static Task<T> F = Task.FromResult(default(T)); // No problem
|
||||
static Task<T> P => Task.FromResult(default(T)); //CS1961
|
||||
static Task<T> M() => Task.FromResult(default(T)); //CS1961
|
||||
static event EventHandler<T> E; // CS1961
|
||||
}
|
||||
```
|
||||
|
||||
Because these members are `static` and non-virtual, there aren't any safety issues here: you can't
|
||||
derive a looser/more restricted member in some fashion by subtyping the interface and overriding
|
||||
the member. We also considered whether this could potentially interfere with some of the other
|
||||
enhancements we hope to make regarding roles, type classes, and extensions. These should all be
|
||||
fine: we won't be able to retcon the existing static members to be virtual-by-default for interfaces,
|
||||
as that would end up being a breaking change on multiple levels, even without changing the variance
|
||||
behavior here.
|
||||
|
||||
We also considered whether this change could be considered a bug fix on top of C# 8, meaning that
|
||||
users would not have to opt into C# 9 in order to see this behavior. While the change is small and
|
||||
likely very rarely needed, we would still prefer to avoid breaking downlevel compilers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will allow `static`, non-virtual members in interfaces to treat type parameters as invariant,
|
||||
regardless of their declared variance, and will ship this change in C# 9.
|
||||
|
||||
### Property Enhancements
|
||||
|
||||
In a [previous LDM](#LDM-2020-04-01.md) we started to look at various enhancements we could make
|
||||
to properties in response to customer feedback. Broadly, we feel that these can be addressed by
|
||||
one or more of the following ideas:
|
||||
|
||||
1. Introduce a `field` contextual keyword that allows the user to refer to the backing storage of
|
||||
the property in the getter/setter of that property.
|
||||
* In this proposal, we can consider all properties today as having this `field` keyword. As
|
||||
an optimization, if the user does not refer to the backing field in the property body, we
|
||||
elide emitting of the field, which happens to be the behavior of all full properties today.
|
||||
* Of the proposals, this allows the most brevity for simple scenarios, allowing some lazily-fetched
|
||||
properties to be one-liners.
|
||||
* This proposal does not move the cliff far: if you need to have a type differing from the
|
||||
type of the property, or multiple fields in a single property, then you must fall back to a full
|
||||
property and expose the backing field to the entire class.
|
||||
* There are also questions about the nullability of these backing properties: must they be
|
||||
initialized? Or should we provide a way to declare them as nullable, despite the property
|
||||
itself not being nullable?
|
||||
* There was also concern that this is adding conceptual overhead for not enough gain: education
|
||||
would be needed on when backing fields are elided and when they are not, complicated by the
|
||||
additional backcompat overhead of ensuring that `field` isn't treated as a keyword when it
|
||||
can bind to an existing name.
|
||||
2. Allow field declarations in properties.
|
||||
* Conveniently for this proposal, a scoping set of braces for properties already exists.
|
||||
* This addresses the issue of properties backed by a different storage type: if you have a
|
||||
property that uses a `Lazy<T>` to initialize itself on first access, for example.
|
||||
* This also allows users to declare multiple backing fields, if they want to lock access
|
||||
to the property or combine multiple pieces of information into a single type in the public
|
||||
surface area.
|
||||
* In terms of user education, this is the simplest proposal. Since a set of braces already
|
||||
exists, the education is just "There's a new scope you can put fields in."
|
||||
3. Introduce a delegated property pattern into the language.
|
||||
* There have been a few proposals for this, such as #2657. Other languages have also adopted
|
||||
this type of feature, including Kotlin and Swift.
|
||||
* This is by far the most complex of the proposals, adding new patterns to the language and
|
||||
requiring declaration of a whole new type in order to remove one or possibly 2 fields from
|
||||
a general class scope.
|
||||
* By that same token, however, this is the most broad of the proposals, allowing users to
|
||||
write reusable property implementations that could be abstracted out.
|
||||
|
||||
The LDM broadly viewed these proposals as increasing in scope: the `field` keyword allows the most
|
||||
brief syntax, but forces users off the cliff back to full class-scoped fields immediately if their
|
||||
use case is not having a single backing field of the same type. Meanwhile, property-scoped fields
|
||||
don't allow for and encourage creating reusable helpers, like delegated properties would.
|
||||
|
||||
We also recognize that regardless of what decisions we make today, we're not done in this space.
|
||||
None of these proposals are mutually exclusive, and we can "turn the crank" by introducing one, and
|
||||
then adding more in a future release. There is interest among multiple LDM members in adding some
|
||||
form of reusable delegated properties or property wrappers, and adding one of either the `field`
|
||||
keyword or property-scoped fields does not preclude adding the other in a later release. Further,
|
||||
all of these proposals are early enough that we still have a bunch of design space to work through
|
||||
with them, while designing ahead enough to ensure that we don't add a wart on the language that we
|
||||
will regret in the future.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
A majority of the LDM members would like to start by exploring the property-scoped locals space.
|
||||
We'll start by expanding that proposal with intent to include in C# 10, but will keep the other
|
||||
proposals in mind as we do so.
|
80
meetings/2020/LDM-2020-06-29.md
Normal file
80
meetings/2020/LDM-2020-06-29.md
Normal file
|
@ -0,0 +1,80 @@
|
|||
# C# Language Design Meeting for June 29th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Static interface members](https://github.com/Partydonk/partydonk/issues/1)
|
||||
|
||||
## Procedural Note
|
||||
|
||||
For the past few years, the LDM notes have been compiled by the wonderful Andy Gocke (@agocke).
|
||||
Andy is taking the next step in his career and moving to be the lead of the CLR App Model team
|
||||
as of today, and as such will be moving to LDT emeritus status, joining the ranks of our other
|
||||
former LDT members who form our advisory council. We thank him for all his hard work during his
|
||||
time as notetaker, collating the sometimes inane rambling of the LDM in a set of readable
|
||||
arguments and decisions.
|
||||
|
||||
At this point Fred Silberberg (@333fred) will be the new LDM notetaker. While I'll try to keep
|
||||
up much of the same spirit as Andy, I am not going to try to match his exact style or formatting,
|
||||
so there could be some changes in the exact formatting of the notes. I'll additionally apologize
|
||||
in advance as the notes inevitably end up delayed in the first few weeks as I settle in. And now,
|
||||
on to the meeting notes!
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"So we spent these last 10 years making it so we can come back to this. We actually never forgot,
|
||||
[.NET Core] has all been a ploy to get statics into interfaces."
|
||||
|
||||
## Discussion
|
||||
|
||||
Today @abock and @migueldeicaza presented their work on Partydonk over the past year, bringing
|
||||
abstract interface members to C# and the Mono runtime in a proof of concept. I did not take notes
|
||||
on the specific slides: all of that material is available publicly on the partydonk repo
|
||||
[here](https://github.com/Partydonk/partydonk/blob/master/Generic%20Math/Generic%20Math%20in%20.NET%20-%20Contractual%20Static%20Interface%20Members%20in%20CSharp.pdf).
|
||||
|
||||
Overall, the LDT members had a very positive reception to this work: in particular, they arrived
|
||||
at many of the same conclusions @CarolEidt had in her exploration of this space 10 years ago:
|
||||
much of the runtime work falls out from allowing `abstract static` in CIL and emitting constrained
|
||||
calls to such members during compilation. From a language perspective, there's still a bit of work
|
||||
to do. `override` on `abstract` static members defined in interfaces isn't particularly nice from
|
||||
a regularity with instance members perspective. We will also have to do design work around explicit
|
||||
implementations: it could all fall out from existing rules, but will need a critical eye to make
|
||||
sure that the consequences of explicitly implementing an abstract member, and what that really means.
|
||||
|
||||
The existing code uses a `TSelf` generic parameter in what could be considered kind of an unfortunate
|
||||
syntax:
|
||||
|
||||
```cs
|
||||
interface INumeric<TSelf> where TSelf : INumeric<TSelf>
|
||||
{
|
||||
abstract static TSelf Zero { get; }
|
||||
abstract static TSelf operator +(TSelf a, TSelf b);
|
||||
}
|
||||
```
|
||||
|
||||
The `TSelf : where TSelf : INumeric<TSelf>` was suggested in the past to help the prototype make
|
||||
progress without blocking on associated types, but it's ugly and proliferates through the entire
|
||||
type hierarchy if left unchecked. A much better solution would be to formally introduce associated
|
||||
types into the language, something that we've discussed in LDM before and has some support. We should
|
||||
take those into account here: if we introduce this entire type hierarchy, then in the next C# release
|
||||
introduce a `TSelf` associated type it would leave an annoying blemish on the language. In particular,
|
||||
we need to make sure we have a good roadmap for all components involved here: the compiler, the runtime,
|
||||
and the core libraries. Nonvirtual static members in interfaces have already shipped with C# 8, so we
|
||||
can't get that back and declared them virtual members of the interface.
|
||||
|
||||
We do still have a few language questions: today, you cannot create user-defined operators that involve
|
||||
interfaces, because doing so would subvert the type system. However, some numeric applications seem
|
||||
like they would want to be able to do this for constants (see `IExpressibleByIntegerLiteral` and
|
||||
`IExpressibleByFloatLiteral` in the presentation). If we allow this for the `TSelf` parameter, it seems
|
||||
like these concerns should be obviated: you're not converting to an interface type, you're converting to
|
||||
a concrete type and specifying that said conversion must be available for any implementations of the
|
||||
interface. Additionally there's an interesting correspondance issue: today, any members that are part
|
||||
of the interface contract are available on an instance of the interface. However, with static methods,
|
||||
the interface itself doesn't provide them: types that implement the interface provide them, but not the
|
||||
interface itself. We can certainly come up with new rules to cover this, but we will need to do so.
|
||||
|
||||
Some other miscellaneous topics that came up:
|
||||
* We could use this system to specify constructors with multiple parameters of a specific type. Static
|
||||
interface members could be implemented by a type calling `new`, which would allow us to improve on the
|
||||
simple `new()` that we have today.
|
||||
* Existing language built-in operators would need to be considered to satisfy the constraints of the
|
||||
numeric interfaces.
|
129
meetings/2020/LDM-2020-07-01.md
Normal file
129
meetings/2020/LDM-2020-07-01.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
# C# Language Design Meeting for July 1st, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Non-defaultable struct types and parameterless struct constructors](#Non-defaultable-struct-types-and-parameterless-struct-constructors)
|
||||
2. [Confirming unspeakable `Clone` method implications](#Confirming-unspeakable-Clone-method-implications)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"I did not say implications, I said machinations [pronounced as in British English]. I used a big word."
|
||||
"You mispronounced machinations [pronounced as in American English], which is why I'm just ignoring you."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Non-defaultable struct types and parameterless struct constructors
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573
|
||||
|
||||
#### Parameterless struct constructors
|
||||
|
||||
We discussed both proposals for allowing default struct constructors and for having a feature to allow differentiating between
|
||||
`struct` types that have a valid `default` value, and types that do not.
|
||||
|
||||
First, we looked at parameterless constructors. Today, `struct`s cannot define their own custom parameterless constructors, and
|
||||
a previous attempt to ship this feature in C# 6 failed due to a framework bug that we could not fix.
|
||||
|
||||
```cs
|
||||
public struct S
|
||||
{
|
||||
public readonly int Field = 1; // Field is set by the default constructor
|
||||
}
|
||||
|
||||
public void M<T>()
|
||||
{
|
||||
var s = (S)Activator.CreateInstance(typeof(T));
|
||||
Console.WriteLine(s.Field);
|
||||
}
|
||||
```
|
||||
|
||||
In .NET Framework and .NET Core prior to 2.0, a call to `M` will print `0`. However, .NET Core fixed this API in 2.0 to correctly
|
||||
call the parameterless constructor of a struct if one is present, and since we now tie language version to the target platform
|
||||
there will be no supported scenario with this bug.
|
||||
|
||||
While there was general support for this scenario in the LDM, we spent most of the time on the second part of the proposal and
|
||||
did not come away with a conclusion for parameterless constructors. We will need to revisit this in context of a reworked defaultable
|
||||
types proposal and make a yes/no conclusion on this feature.
|
||||
|
||||
#### Non-defaultable struct types
|
||||
|
||||
The crux of this proposal is that we would extend the tracking we introduced in C# 8 with nullable reference types, and extend it
|
||||
to value types that opt-in, holes and all. From a type theory perspective, the idea is that by applying a specific attribute, a
|
||||
struct type can indicate that `default` and `new()` are _not_ the same value in the domain of its type. In fact, if the struct does
|
||||
not provide a parameterless constructor, `new()` wouldn't be in the domain of the struct at all. This attribute would further opt the
|
||||
struct's `default` value into participating in "invalid" scenarios in the same way that `null` is part of the domain of a reference
|
||||
type, but is considered invalid for accessing members directly on that instance. This played well with our previous design of `T??`,
|
||||
if we were to allow the `??` moniker on types constrained to `struct` as well as unconstrained type parameters. However, as `??`
|
||||
has been removed from C# 9 due to syntactic ambiguities (notes [here](LDM-2020-06-17.md#T??)), that part of the proposal will have
|
||||
to be reworked. Not having `??` makes the feature much harder to explain to users, and we'll run into issues with representation in
|
||||
non-generic scenarios.
|
||||
|
||||
One thing that is clear from discussion is that non-defaultable struct types will need to have some standardized form of checking
|
||||
whether they are the default instance. `ImmutableArray<T>`, for example, has an `IsDefault` property, as do most of the existing
|
||||
struct types in Roslyn that cannot be used when `default`. We would want to be able to recognize this pattern in nullable analysis,
|
||||
just like we do today with the `is null` pattern. Since the attribute and pattern would be new, we could declare it to be whatever
|
||||
we desire, and the libraries will standardize around that if they want to participate.
|
||||
|
||||
Generics also present an interesting challenge for non-defaultable value types. Today, the `struct` constraint implies new:
|
||||
|
||||
```cs
|
||||
public void M<T>() where T : struct
|
||||
{
|
||||
var y = new T(); // Perfectly valid
|
||||
}
|
||||
```
|
||||
|
||||
If we were to enable non-defaultable struct types, this would change: `new()` is not necessarily valid on all struct types because
|
||||
non-defaultable struct types have explicitly opted to separate `default` and `new()` in their domain, and might not have provided
|
||||
a value for `new()`, meaning that it would return `default`. From an emit perspective, this is further complicated: for the above
|
||||
code, the C# compiler _already_ emits a `new()` constraint. C# code cannot actually specify both the `struct` constraint and the
|
||||
`new()` constraint at the same time today, but in order to actually emit the combination of these constraints for this feature
|
||||
we would have to introduce a new annotation on the type parameter to describe that it is required to have a parameterless `new()`
|
||||
that provides a valid instance of the type.
|
||||
|
||||
`ref` fields in structs also came up in discussion. This is a feature that we've been asked for by the runtime team and a few other
|
||||
performance-focussed areas, but is very hard to represent in C# because it would require a hard guarantee that a struct with a
|
||||
`ref` field is truly never defaulted, by anything. `ref`s do not have a "default", so a struct that contained on in a field would
|
||||
need to not be possible to default in any fashion. This proposal could overlap with that feature: the guarantees provided here are
|
||||
no stronger than the guarantees given with nullable reference types, which is to say easy to break: arrays of these structs would
|
||||
still be filled with `default` on creation, for example, even if the type wasn't annotated with a `??` or hypothetical other sigil.
|
||||
We need to be sure that, if that's the case and we do want to add `ref` fields, we're comfortable having both a "soft" and "hard"
|
||||
defaultness guarantee in the language.
|
||||
|
||||
Finally, there was some recognition and discussion around how this issue is very similar to another long standing request from
|
||||
libraries like Unity: custom nullability. The idea is that with C#, among the entire value domain of a type we recognize and have
|
||||
built language support for one _particular_ invalid value: `null`. However, this isn't the only invalid value that a value domain
|
||||
may have. Unity objects have backing C++ instances, and they override the `==` operator to allow comparison with `null` to also
|
||||
return true if the backing field has not yet been instantiated. While the C# object itself is not `null` in this case, it _is_
|
||||
invalid, and should be treated as such. However, this doesn't play well with other operators in C#, such as `?.`, `??`, and
|
||||
`is null`. These all special-case a particular invalid value, `null`, and don't play well with other invalid values, leading
|
||||
libraries like Unity to encourage users to write code that does not take advantage of modern idiomatic C# features. This issue is
|
||||
very similar to the non-defaultable structs issue: we'd like to recognize a particular value in the domain of a struct type as
|
||||
invalid. It might be better to implement this as a general invalid value pattern that any type, struct or class, can opt into.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
For both of these issues, we need to take more time and rethink them again, especially in light of the removal of `??`, which
|
||||
the non-defaultable struct type proposal relied on heavily. A small group will explore the space more, particularly the more
|
||||
general invalid object pattern, and come back with a rethought proposal. The guiding principle that this group should keep in
|
||||
mind from the current proposal is "Users should be able to change something from a class to a struct for performance without
|
||||
significant redesign due to having to handle an invalid `default` struct value."
|
||||
|
||||
### Confirming unspeakable `Clone` method implications
|
||||
|
||||
Before we ship unspeakable `Clone` methods for `with` expressions, we wanted to make sure that we've worked through the
|
||||
consequences of doing so, and are sure that the language will be able to continue to evolve without breaking scenarios that
|
||||
we are enabling with this feature. In particular, in the face of a general factory pattern that users can use to extend record
|
||||
types, or even potentially expand what is today a record type into a full blown type without breaking their customers, we
|
||||
might need to emit both the unspeakable `Clone` method and a factory method in the future. A guiding principle for record design
|
||||
has been that whether something is a record is an implementation detail. Therefore whatever future method we add that will allow
|
||||
a regular class type to participate in a `with` expression will likely have to emit this method as well.
|
||||
|
||||
We also considered whether we should take any measures right now to try and keep our design space open in records for adding a
|
||||
user-overridable `Clone` method. We could try emitting the method now, and modreq it so that it cannot be directly called from
|
||||
C# code, or we could just block users from creating a `Clone` method in a record entirely.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're fine with the unspeakable name being a feature of records forever going forward. We will also reserve `Clone` as a member
|
||||
name in records to ensure that our future selves will be able to design in this space.
|
126
meetings/2020/LDM-2020-07-06.md
Normal file
126
meetings/2020/LDM-2020-07-06.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
|
||||
# C# Language Design Meeting for July 6th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Repeated attributes on `partial` members](#repeated-attributes-on-partial-members)
|
||||
2. [`sealed` on `data` members](#sealed-on-data-members)
|
||||
3. [Required properties](#required-properties)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"Is there a rubber stamp icon I can use here?"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Repeated attributes on `partial` members
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3646
|
||||
|
||||
Today, when we encounter attribute definitions among partial member definitions, we merge these attributes, applying attributes multiple
|
||||
times if there are duplicates across the definitions. However, if there are duplicated attributes that do not allow multiples, this merging
|
||||
results in an error that might not be resolvable by the user. For example, a source generator might copy over the nullable-related
|
||||
attributes from the stub side to the implementation side. This is further exacerbated by the new partial method model: previously, the
|
||||
primary generator of partial stubs was the code generator itself. WPF or other code generators would generate the partial stub, which the
|
||||
user could then implement to actually create the implementation. These generators generally wouldn't add any attributes, and the user could
|
||||
add whatever attributes they wanted. However, the model is flipped for the newer source generators. Users will put attributes on the stub in
|
||||
order to hint to the generator how to actually implement the method, so either the generator will have never copy attributes over (possibly
|
||||
complicating implementation), or it will have to be smart about only copying over attributes that matter. It would further hurt debugability
|
||||
as well; presumably tooling will want to show the actual implementation of the method when debugging, and it's likely that the tooling won't
|
||||
want to try and compute the merged set of attributes from the stub and the implementation to show in debugging.
|
||||
|
||||
What emerged from this discussion is a clear divide in how members of the LDT view the stub and the implementation of a partial member: some
|
||||
members view the stub as a hint that something like this method exists, and the implementation provides the final source of truth. This group
|
||||
would expect that, if we were designing again, all attributes would need to be copied to the implementation and attributes on the stub would
|
||||
effectively be ignored. The other segment of the LDT viewed partial methods in exactly the opposite way: the stub is the source of truth, and
|
||||
the implementation had better conform to the stub. This reflects these two worlds of previous source generators vs current generators: for the
|
||||
previous uses of partial, the user would actually be creating the implementation, so that's the source of truth. For the new uses, the user is
|
||||
creating the stubs, so that's the source of truth.
|
||||
|
||||
We also briefly considered a few ways of enabling the attribute that keys the generator to be removed from the compiled binary, so that it
|
||||
does not bloat the metadata. However, we feel that that's a broader feature that's been on the backlog for a while, source-only attributes. We
|
||||
don't see anything in this feature conflicting with source-only attributes. We also don't see anything in this feature conflicting with
|
||||
future expansions to partial members, such as partial properties.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The proposal is accepted. For the two open questions:
|
||||
|
||||
1. We will deduplicate across partial types as well as partial methods if `AllowMultiple` is false. This is considered lower priority if a
|
||||
feature needs to be cut from the C# 9 timeframe.
|
||||
2. We don't have a good use-case for enabling `AllowMultiple = true` attributes to opt into deduplication. If we encounter scenarios where
|
||||
this is needed, we can take it up again at that point.
|
||||
|
||||
### `sealed` on `data` members
|
||||
|
||||
In a [previous LDM](LDM-2020-06-22.md#data-properties), we allowed an explicit set of attributes on `data` members, but did not include
|
||||
`sealed` in that list, despite allowing `new`, `override`, `virtual`, and `abstract`. `sealed` feels like it's missing, as it's also
|
||||
pertaining to inheritance.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
`sealed` will be allowed on `data` properties.
|
||||
|
||||
### Required properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
In C# 9, we'll be shipping a set of improvements to nominal construction scenarios. These will allow creation of immutable objects via
|
||||
object initializers, which has some advantages over positional object creation, such as not requiring exponential constructor growth
|
||||
over object hierarchies and the ability to add a property to a base class without breaking all derived types. However, we're still
|
||||
missing one major feature that positional constructors and methods have: requiredness. In C# today, there is no way to express that a
|
||||
property must be set by a consumer, rather than by the class creator. In fact, there is no requirement that all fields of a class type
|
||||
need to be initialized during object creation: any that aren't initialized are defaulted today. The nullable feature will issue warnings
|
||||
for initialized fields inside the declaration of a class, but there is no way to indicate to the feature that this field must be initialized
|
||||
by the consumer of the class. This goes further than just the newly added `init`: mutable properties should be able to participate in this
|
||||
feature as well. In order for staged initialization to feel like a true first-class citizen in the language, we need to support requiredness
|
||||
in the contract of creating a class via the feature.
|
||||
|
||||
The LDM has seemingly universal support of making improvements here. In particular, the proposed concept of "initialization debt" resonated
|
||||
strongly with members. It allows for a general purpose mechanism that seems like it will extend natural to differing initialization modes.
|
||||
We categorized the two extreme ends of object initialization, both of which can easily be present in a single nominal record: Nothing is
|
||||
initialized in the constructor (the default constructor), and everything is initialized in the constructor (the copy constructor). The next
|
||||
question is how are these initialization contracts created: there's some tension here with the initial goal of nominal construction.
|
||||
|
||||
Fundamentally, initialization contracts can be derived in one of two ways: implicitly, or explicitly. Implicit contracts are attractive at
|
||||
first glance: they require little typing, and importantly they're not brittle to the addition of new properties on a base class, which was
|
||||
an important goal of nominal creation in the first place. However, they also have some serious downsides: In C# today, public contracts for
|
||||
consumers are always explicit. We don't have inferred field types or public-facing parameter/return types on methods/properties. This means
|
||||
that any changes to the public contract of an object are obvious when reviewing changes to that type. Implicit contracts change that. It
|
||||
would be very possible for a manually-implemented copy constructor on a derived type to miss adding a copy when a property is added to its
|
||||
base type, and suddenly all uses of `with` on that type are now broken.
|
||||
|
||||
We further observe that this shouldn't just be limited to auto-properties: a non-autoprop should be able to be marked as required, and then
|
||||
any fields that the initer or setter for that property initializes can be considered part of the "constructor body" for the purposes of
|
||||
determining if a field has been initialized. Fields should be able to be required as well. This could extend well to structs: currently,
|
||||
struct constructors are required to set all fields. If they can instead mark a field or property as required then the constructor wouldn't
|
||||
have to initialize it all.
|
||||
|
||||
One way of implementing initialization debt would be to tie it to nullable: it already warns about lack of initialization for not null
|
||||
fields when the feature is turned on. We're still in the nullable adoption phase where we have more leeway on changing warnings, so we
|
||||
could actually change the warning to warn on _all_ fields, nullable or not. This would effectively be an expansion of definite assignment:
|
||||
locals must be assigned a value before use, even if that value is the default. By extending that requirement to all fields in a class, we
|
||||
could essentially make all fields required when nullable is enabled, regardless of their type. Then, C# 10 could add a feature to enable
|
||||
skipping the initialization of some members based on whether the consumer must initialize them. This is also not really a breaking change
|
||||
for structs: they're already required to initialize all fields in the constructor. However, it would be a breaking change for classes, and
|
||||
we worry it would be a significantly painful one, especially with no ability to ship another preview before C# 9 releases. `= null!;` is
|
||||
already a significant pain point in the community, and this would only make it worse.
|
||||
|
||||
We came up with a few different ways to mark a property as being required:
|
||||
* A keyword, as in the initial proposal, on individual properties.
|
||||
* Assigning some invalid value to the field/property. This could work well as a way to be explicit about what fields a particular
|
||||
constructor would require, but does leave the issue about inherited brittleness.
|
||||
* Attributes or other syntax on constructor bodies to indicate required properties.
|
||||
|
||||
We like the idea of some indication on a property itself dictating the requiredness. This puts all important parts of a declaration together,
|
||||
enhancing readability. We think this can be combined with a defaulting mechanism: the property sets whether it is required, and then a
|
||||
constructor can have a set of levers to override individual properties. These levers could go in multiple ways: a copy constructor could
|
||||
say that it initializes _all_ members, without having to name individual members, whereas a constructor that initializes one or two specific
|
||||
members could say it only initializes those specific members, and inherits the property defaults from their declarations. There's still
|
||||
open questions in this proposal, but it's a promising first start.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We have unified consensus that in order for staged initialization to truly feel first-class in the language, we need a solution to this issue,
|
||||
but we don't have anything concrete enough yet to make real decisions. A small group will move forward with brainstorming and come back to
|
||||
LDM with a fully-fleshed-out proposal for consideration.
|
213
meetings/2020/LDM-2020-07-13.md
Normal file
213
meetings/2020/LDM-2020-07-13.md
Normal file
|
@ -0,0 +1,213 @@
|
|||
# C# Language Design Meeting for July 13th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Generics and generic type parameters in aliases](#Generics-and-generic-type-parameters-in-aliases)
|
||||
2. ["closed" enum types](#"closed"-enum-types)
|
||||
3. [Allow `ref` assignment for switch expressions](#Allow-`ref`-assignment-for-switch-expressions)
|
||||
4. [Null suppressions nested under propagation operators](#Null-suppressions-nested-under-propagation-operators)
|
||||
5. [Relax rules for trailing whitespaces in format specifier](#Relax-rules-for-trailing-whitespaces-in-format-specifier)
|
||||
6. [Private field consideration in structs during definite assignment analysis](#Private-field-consideration-in-structs-during-definite-assignment-analysis)
|
||||
7. [List patterns](#list-patterns)
|
||||
8. [Property-scoped fields and the `field` keyword](#Property-scoped-fields-and-the-field-keyword)
|
||||
9. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
10. [Allowing `ref`/`out` on lambda parameters without explicit type](#Allowing-ref/out-on-lambda-parameters-without-explicit-type)
|
||||
11. [Using declarations with a discard](#Using-declarations-with-a-discard)
|
||||
12. [Allow null-conditional operator on the left hand side of an assignment](#Allow-null-conditional-operator-on-the-left-hand-side-of-an-assignment)
|
||||
12. [Top level statements and functions](#Top-level-statements-and-functions)
|
||||
13. [Implicit usings](#implicit-usings)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
"It does actually seem like this pattern is the 98% case..."
|
||||
"I just want to disagree with something [redacted] said... [they] said [they] thought it was the 98% case that this would apply to, and I think it's 99..."
|
||||
"Not fair, I was going to say that."
|
||||
|
||||
## Discussion
|
||||
|
||||
This meeting was issue triage. There will not be much detail on any particular issue, just a general summary
|
||||
of the LDM's feeling and the milestone that we triaged to.
|
||||
|
||||
### Generics and generic type parameters in aliases
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1239
|
||||
|
||||
In general, we want to make improvements in this area. We should also be open to making other enhancements
|
||||
in this space, such as allowing tuple syntax on the right side, or allowing C# keywords like `int`. There's
|
||||
also a set of features that we should investigate in parallel, such as globally-visible aliases or publicly-
|
||||
exported aliases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for C# 10 for now so that we can look at the space hollistically in the near term.
|
||||
|
||||
### "closed" enum types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3179
|
||||
|
||||
This is a solution to the very common complaints around enum usage, particularly in switch expressions and other
|
||||
exhaustiveness scenarios. It will also work well with DUs, and we should make sure that whatever syntax we use
|
||||
there works well with closed or sealed enum types as well.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for C# 10 to look at in coordination with DUs.
|
||||
|
||||
### Allow `ref` assignment for switch expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3326
|
||||
|
||||
We like this proposal. You can already do this with ternaries, and it's odd that you can't do this with switch
|
||||
expressions as well. It's not high priority, but we'd be happy to see it in the language. Like ternaries, the
|
||||
ref version would not be target-typed.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for Any Time.
|
||||
|
||||
### Null suppressions nested under propagation operators
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
This will be a breaking change, so we need to get it in as soon as possible.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
C# 9 timeframe. We need to make a syntax proposal and approve it.
|
||||
|
||||
### Relax rules for trailing whitespaces in format specifier
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3414
|
||||
|
||||
In reading this proposal, we're unsure whether the request was for _ignoring_ trailing spaces in format specifiers,
|
||||
or allowing them and including them in the format specifier. We think that either way this is interpreted, it will
|
||||
be confusing: trailing spaces are allowed in front of an interpolated expression and mean nothing, but for things
|
||||
like `DateTime`, spaces are valid parts of a format specifier and will be respected in the output. We think that
|
||||
either behavior here would be confusing, with no clear indication on which the user wants.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
### Private field consideration in structs during definite assignment analysis
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3431
|
||||
|
||||
This is a bug from the native compiler that had to be reimplemented in Roslyn for compat, but it's always been
|
||||
one of the prime candidates for a warning wave (and is already supported as an error in the compiler via the strict
|
||||
feature flag). There is some contention whether it should be a warning or an error in this wave. There is also a
|
||||
concern that this will combine with the `SkipLocalsInit` feature in C# 9 to expose an uninitialized memory hole in
|
||||
C# with no use of `unsafe` or `System.Unsafe` required.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We need to look at this ASAP to make sure that we don't unintentionally expose unsafety in the language without
|
||||
user intention. We'll schedule this for next week.
|
||||
|
||||
### Remove restriction that optional parameters must be trailing
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3434
|
||||
|
||||
It's possible to define such methods in metadata with the correct attribute usage _and_ call them from C# today.
|
||||
This would just be about removing the restriction from _defining_ them in C#.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for Any Time.
|
||||
|
||||
### List patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3435
|
||||
|
||||
We have an open proposal that with a prototype. We like the general direction it takes, but we need to have more
|
||||
detailed design review of it and possibly make a few different decisions. This is a direction we want to take
|
||||
pattern matching in future releases, so we'll continue iterating on this.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10 for design work.
|
||||
|
||||
### Property-scoped fields and the `field` keyword
|
||||
|
||||
* https://github.com/dotnet/csharplang/issues/133
|
||||
* https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
We've started doing design work around both of these issues. We should continue doing that.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10.
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
This has been a large request for a long time that reflects the default of almost every single C# file written.
|
||||
There's still some design work to do, particularly in how that will affect `using` statements, but it's work
|
||||
that we should take on.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10.
|
||||
|
||||
### Allowing `ref`/`out` on lambda parameters without explicit type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/338
|
||||
|
||||
We've heard this request a few times, and while we don't see any particular issue with this, it's not a priority
|
||||
for the team at this point. If a spec/implementation was provided we'd likely accept it but won't go out of our
|
||||
way to add it unless something else changes here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged to Any Time.
|
||||
|
||||
### Using declarations with a discard
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2235
|
||||
|
||||
There's still some open design questions here, particularly around possibly ambiguous syntaxes. We need to keep
|
||||
iterating on this to find a design that we like.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for X.X, pending a new proposal.
|
||||
|
||||
### Allow null-conditional operator on the left hand side of an assignment
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2883
|
||||
|
||||
Initial discussion shows a big LDT split on whether `?.` should be able to effectively a conditional ref. We
|
||||
would need to discuss further, but aren't ready to outright approve or reject the feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage to X.X for future discussion.
|
||||
|
||||
### Top level statements and functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
This is part 2 of the work we started in C# 9 with top-level statements: designing free-floating functions that
|
||||
can sit on the top level without any containing type. We need to continue examining this context of scripting
|
||||
unification, which we plan to continue doing on an ongoing basis.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage into C# 10 for ongoing discussion.
|
||||
|
||||
### Implicit usings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
This is another issue arising of discussions on top-level statements and using C# as a scripting language:
|
||||
attempting to make the language more brief by specifying common imports on the command line or as part of
|
||||
the project file is certainly one way to remove boilerplate. However, it's a controversial feature that often
|
||||
draws visceral reactions. We need to discuss this more and figure out if it's a feature we want to have in C#,
|
||||
taking into account our increased focus on scripting-like scenarios. It's worth noting that CSX has a limited
|
||||
form of this support already, and implementing this in C# proper would bring us one step closer to unifying the
|
||||
two dialects.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage to C# 10 for discussion.
|
113
meetings/2020/LDM-2020-07-20.md
Normal file
113
meetings/2020/LDM-2020-07-20.md
Normal file
|
@ -0,0 +1,113 @@
|
|||
# C# Language Design Meeting for July 20th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Finishing Triage](#finishing-triage)
|
||||
a. [Extend with expression to anonymous type](#Extend-with-expression-to-anonymous-type)
|
||||
b. [Required properties](#Required-properties)
|
||||
c. [Shebang support](#shebang-support)
|
||||
2. [Private reference fields in structs with `SkipLocalsInit`](#Private-reference-fields-in-structs-with-`SkipLocalsInit`)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"If you go get a beer from the fridge or wherever you keep them and drink it right now, I'll refund you."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Finishing Triage
|
||||
|
||||
#### Extend with expression to anonymous type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3530
|
||||
|
||||
We don't see any reason why we couldn't extend `with` expressions to handle anonymous types, and we universally support
|
||||
the idea. We do see room for further improvement as well. Struct types should be generally possible to `with`, and in
|
||||
particular tuples should feel first class here. Some other ideas for future improvements that we'll need to keep in mind
|
||||
when designing anonymous types is an ability to specify a `withable` constraint: perhaps that's a thing we could do via
|
||||
typeclasses, but if that's a thing that should be specifiable then we'll want to make sure that whatever we do for structs
|
||||
and anonymous types works well with it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into C# 10 for discussion with the other `with` enhancements
|
||||
|
||||
#### Required properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
A smaller group is currently fleshing this proposal out in the direction of initializer debt, and is looking to talk more
|
||||
about it in the next few months.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged for C# 10.
|
||||
|
||||
#### Shebang support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3507
|
||||
|
||||
This is part of the next step in the C# scripting discussion. In particular, this proposal details just one, very small
|
||||
piece of the puzzle here, namely the ability have a C# file startup an environment. There is significantly more work to
|
||||
be done in the language and tooling to effectively modernize the loose-files support for C#. Today, C# code has a core
|
||||
concept that cs files themselves do not contain information about the runtime environment. They don't specify the target
|
||||
runtime, nuget dependencies, where tools can be found, or anything else similar (beyond some `#ifdef` blocks). Supporting
|
||||
this would be intentionally eroding this aspect, which we need to make sure we're doing with intention and design. There
|
||||
is support among LDT members for this scenario, so we'll continue to look at.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into C# 10 for discussion. We acknowledge that we may well not ship anything in this space as part of C# 10, but
|
||||
we want to start discussing possible futures here in the coming X months, not X years.
|
||||
|
||||
### Private fields in structs with `SkipLocalsInit`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3431
|
||||
|
||||
Proposal 1: https://github.com/dotnet/roslyn/issues/30194#issuecomment-657858716
|
||||
Proposal 2: https://github.com/dotnet/roslyn/issues/30194#issuecomment-657900257
|
||||
|
||||
This topic was brought up last week when we realized there was a potential hole in `SkipLocalsInit` with definite
|
||||
assignment, where we realized it's possible to observe uninitialized memory via private fields. When importing metadata,
|
||||
the native compiler ignored private fields, including private fields in structs, and did not require them to be considered
|
||||
definitely assigned before usage. This behavior was preserved when we implemented Roslyn, as attempting to break it was
|
||||
too large of a change for organizations to adopt. Both of these proposals ensure that, with `SkipLocalsInit` on a method,
|
||||
it's considered an error to not definitely assign here, which was fine with LDM as this is a new feature and cannot break
|
||||
anyone. We then looked at the differences between the two proposals, namely whether the definite assignment diagnostic
|
||||
should be a warning or an error when users opt into to a new warning wave. We found the arguments around incremental
|
||||
adoption compelling: we don't want users to be blocked off from adopting new warning waves and making their code at least
|
||||
a little safer by issuing errors that cannot be ignored. If users want to ensure that these warnings are fixed in their
|
||||
code, they can use `warnAsError` to turn these specific warnings or all warnings into errors, as you can today.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Proposal 1 has been chosen:
|
||||
|
||||
1. If a method has the [SkipLocalsInit] attribute, or the compilation has the "strict" feature enabled, we use the strong
|
||||
version of analysis for its body. Otherwise
|
||||
2. If a sufficiently high /warnversion (>= 5) is enabled, we run the strong version of analysis, and
|
||||
a. If it reports no diagnostics, we are done.
|
||||
b. If it reports diagnostics, we run the weak version of analysis. Errors that are common to the two runs are reported
|
||||
as errors, and errors only reported by the strong version are downgraded to a warning.
|
||||
3. Otherwise we run the weak version of analysis.
|
||||
|
||||
### Future record direction
|
||||
|
||||
See https://github.com/dotnet/csharplang/issues/3707 for the full list. We briefly went through the list here to gauge
|
||||
support from LDT members on the various proposals. We didn't get particularly in depth on any one part, but some
|
||||
highlights:
|
||||
|
||||
* `init` fields and methods - We rejected these in 9, deciding to wait for community feedback. We believe this is still
|
||||
the right decision, and want to hear user scenarios before proceeding further.
|
||||
* Final initializers - We briefly entertained the idea of having an `init { }` syntax to guarantee that a block is called
|
||||
after a method. This was later rejected as too complicated after design review. Like `init`, we need to get a better
|
||||
handle on the user scenarios.
|
||||
* Required members - We have broad support for doing something in this space.
|
||||
* Factores - We have broad support for doing something in this space.
|
||||
* User-defined cloning - Will likely need to be designed hand-in-hand with factories
|
||||
* Cross-inheritance between records and non-records - part of the initial record goal was that `record` is pure syntactic
|
||||
sugar. If that's still a goal, then we need to do more work here.
|
||||
* Generalized primary constructors - Mixed support here. We need to do more design work.
|
||||
* Primary constructor bodies - Might be done for the former. We need to flesh this out so we can determine how to apply
|
||||
attributes to record constructors.
|
||||
* Discriminated unions - We need to determine whether this will be a simple syntactic shorthand for a general sealed type
|
||||
hierarchy feature, or if this will be the only way to declare such hierarchies.
|
186
meetings/2020/LDM-2020-07-27.md
Normal file
186
meetings/2020/LDM-2020-07-27.md
Normal file
|
@ -0,0 +1,186 @@
|
|||
# C# Language Design Meeting for July 27th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [Improved nullable analysis in constructors](#Improved-nullable-analysis-in-constructors)
|
||||
- [Equality operators in records](#Equality-operators-in-records)
|
||||
- [`.ToString()` or `GetDebuggerDisplay()` on records](#ToString-or-DebuggerDisplay-on-record-types)
|
||||
- [W-warnings for `T t = default;`](#W-warnings-for-`T-t-=-default;`)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"We should pick our poison: one is arsenic, and will kill us. The other is a sweet champagne, and will give us a headache tomorrow."
|
||||
|
||||
![Delectable Tea or Deadly Poison](delectable_tea_2020_07_27.png)
|
||||
|
||||
## Procedural Note
|
||||
|
||||
LDM is going on August hiatus as various members will be out. We'll meet next on August 24th, 2020.
|
||||
|
||||
## Discussion
|
||||
|
||||
### Improved nullable analysis in constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/nullable-constructor-analysis.md
|
||||
|
||||
This proposal is a tweak to the nullable warnings we emit today in constructors that would bring these warnings more in line with
|
||||
behavior for `MemberNotNull`, as well as addressing a few surprising cases where you do not get a deserved nullable warning today.
|
||||
This code, for example, has no warnings today:
|
||||
```cs
|
||||
public class C
|
||||
{
|
||||
public string Prop { get; set; }
|
||||
public C() // we get no "uninitialized member" warning, as expected
|
||||
{
|
||||
Prop.ToString(); // unexpectedly, there is no "possible null receiver" warning here
|
||||
Prop = "";
|
||||
}
|
||||
}
|
||||
```
|
||||
We consider this to be possible to retrofit into the nullable feature, as we're still within the nullable adoption phase. After
|
||||
C# 9 is released, however, this would be a breaking change, so we will need to either do it now, or do it in a warning wave
|
||||
(which would heavily complicate the implementation). Additionally, even though we're still within the nullable adoption phase,
|
||||
this is a relatively big change in warnings that could result in lots of new errors in codebases that have heavily adopted nullable.
|
||||
We also considered whether there could be any interference here with C# 10 plans around required properties or initialization debt.
|
||||
There does not appear to be, as this would effectively be the initialization debt world where a constructor is required to set all
|
||||
properties in the body, and any relaxation of this by requiring properties to be initialized at construction site will play well.
|
||||
|
||||
#### Conclusion:
|
||||
|
||||
We should implement the change and smoke test it on roslyn, the runtime repo, and on VS. If there is large churn, we'll re-evaluate
|
||||
at that point.
|
||||
|
||||
### Equality operators in records
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3707#issuecomment-661800278
|
||||
|
||||
We've talked briefly about equality operators in records prior to now, but we never ended up making any firm decisions, so now we
|
||||
need to make an intentional decision about records before we ship as doing so after would be a breaking change. The concern is that,
|
||||
because class types inherit a default equality operator from simply being a class, we'll get into a scenario where a record's `Equals`
|
||||
method could return `true`, and the `==` operator returns false. Most standard .NET "values" behave in this fashion as well: `string`
|
||||
being the most common example.
|
||||
|
||||
There are some interesting niggles, however, particularly with `float` and `double` values. For example,
|
||||
this code:
|
||||
```cs
|
||||
var a = (float.NaN, double.NaN);
|
||||
System.Console.WriteLine(a == a); // Prints false
|
||||
System.Console.WriteLine(a.Equals(a)); // Prints true
|
||||
```
|
||||
This happens because the `==` operator and the `Equals` method for floats and doubles behave differently. The `==` operator uses IEEE
|
||||
equality, which does not consider `NaN` equal to itself, while `Equals` uses the CLR definition of equality, which does. When combined
|
||||
with `ValueTuple`, this makes for an interesting set of behaviors, as `ValueTuple` doesn't have its own definition of the `==` operator.
|
||||
Rather, the compiler synthesizes the set of `==` operators on the component elements of the tuple. This means that for generic cases,
|
||||
such as `public void M<T>((T, T) tuple) { _ = tuple == tuple; }`, you get an error when attempting to use `==` on the tuple, since `==`
|
||||
is not defined on generic type parameters. `ValueTuple` is a particularly special case here though. The compiler special cases knowledge
|
||||
of the implementation, which is not generalizable across inheritance hierarchies with record types. In order for an implementation to do
|
||||
memberwise `==` across all levels of a record type, there will have to be hidden virtual methods to take care of this, and it gets more
|
||||
complicated when you consider that a derived record type could introduce a generic type parameter field that can't be `==`'d. We feel
|
||||
that attempting to get too clever here would end up being unexplainable, so if we want to implement the operators, it should just
|
||||
delegate to the virtual `Equals` methods we already set up.
|
||||
|
||||
Next, we considered whether we should implement `==` operators on all levels of a record inheritance hierarchy, or just the top level.
|
||||
With records as they're shipping in C# 9, we don't believe that there's a scenario you can get into that would break this. However, the
|
||||
eventual goal is to enable records and classes to inherit from each other, and that point if every level didn't provide its own
|
||||
implementation it could end up being possible to break assumptions. Further, attempting to trim implementations of `==` and `!=` would
|
||||
end up resulting in complicated rules that don't feel necessary: adding the operators isn't going to bloat metadata size, will potentially
|
||||
allow eliminating of some levels of equality method calls, and future proofs ourselves.
|
||||
|
||||
Finally, we looked at whether the user will be able to provide their own implementation of `==` and `!=` operators, and if we'd error
|
||||
on this or allow it and not generate the operators ourselves in this case. We feel that allowing this would complicate records: there
|
||||
are existing extension points into record equality that can be overridden as needed, and we want to have a stronger concept of `Equals`
|
||||
and `==` being the same. If there are good user scenarios around allowing this, we can consider it at a later point.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will generate the `==` and `!=` operators on every level of a record hierarchy. These implementations will not be user-overridable,
|
||||
and they will delegate to `.Equals`. The implementation will have this semantic meaning:
|
||||
```cs
|
||||
pubic static bool operator==(R? one, R? other)
|
||||
=> (object)one == other || (one?.Equals(other) ?? false);
|
||||
public static bool operator!=(R? one, R? other)
|
||||
=> !(one == other);
|
||||
```
|
||||
|
||||
### `ToString` or `DebuggerDisplay` on record types
|
||||
|
||||
#### Initial proposal
|
||||
|
||||
1. `record` generates a ToString that prints out its type name followed by a positional syntax: Point(X: 1, Y: 2).
|
||||
(issue: disfavors nominal records)
|
||||
2. `record` generates a ToString that does the above and also appends nominal properties by calling ToStringNominal:
|
||||
`FirstName: First, LastName: Last`, and assembles the parts into `Person { FirstName: First, LastName: Last, ID: 42 }`.
|
||||
|
||||
#### Discussion
|
||||
|
||||
The initial proposal here is to add a `ToString` method that would format a record type by their positional properties, and have a
|
||||
separate method for creating a string from nominal properties named something like `ToStringNominal`. The initial reaction was that
|
||||
this seemed overly complicated: there should just be one method that does "the right thing<sup>tm</sup>". There's further question
|
||||
about whether having a `ToString` or `DebuggerDisplay` would be useful: depending on the properties of the record type, it could end
|
||||
up doing more harm than good (if the properties were lazy, for example). Additionally, when a `ToString` is provided it's often domain
|
||||
specific, and nothing we do in the compiler would be able to predict what shape that should be. We ended up coming up with a list of
|
||||
common scenarios in which a `ToString` would be useful:
|
||||
|
||||
* Viewing in the debugger. This could be done with just a `DebuggerDisplay` attribute, or users can just expand the object to view the
|
||||
properties of it as you can today.
|
||||
* Viewing in test output. Using an equality test, for example, will usually print the objects if they were not equal to each other,
|
||||
and it would be useful if that was actually meaningful, particularly for these value-like class types.
|
||||
* Logging output. This is a very similar case to the previous.
|
||||
|
||||
We also considered whether we should implement this via source generators. However, it feels very similar to equality: we have a
|
||||
sensible default there, and anyone who wants to customize (this field should be excluded, this one should use sequence equality)
|
||||
can either write it manually or use a source generator. The same arguments apply here: we can provide a sensible default that will
|
||||
enable short syntax, and anyone who wants to get custom behavior can override this.
|
||||
|
||||
After considering _whether_ to do this feature, we looked at what properties would be included in such a feature. How, would they
|
||||
be formatted, and which ones are considered? The initial proposal was for positional only, with a separate method for nominal
|
||||
properties. We felt that this was too complicated and likely not to be the right defaults: it totally disadvantages nominal records
|
||||
and makes the implementation more complicated. The options we considered are:
|
||||
|
||||
* The same members as .Equals. This means all fields of a class, translating auto-generated backing fields to their properties for
|
||||
display names. This would make explaining what appears in the `ToString` simple: it's the equality members, full stop.
|
||||
* The positional members and `data` members of a record. We feel that this is too restrictive, especially since `data` members will
|
||||
not be shipping in final form with C# 9: they'll still be under the preview language version.
|
||||
* The fields and properties of a record type with some accessibility, accessibility to be determined next. We believe that it's very
|
||||
unusual for a `ToString` to display `private` fields, which the other proposal would do.
|
||||
|
||||
We leaned towards just doing the fields and properties of a type, with some accessibility. Finally, we looked at what accessibility
|
||||
to use by default for these:
|
||||
|
||||
* All members not marked `private`. This means that things that not relevant to a consumer of the type, such as `protected` fields,
|
||||
show up in the `ToString`, and violates encapsulation principles.
|
||||
* All `public` and `internal` members. This propsal has some of the same issues as the previous one: you could making an `internal`
|
||||
type that implements a `public` interface, and it would be odd if the final user sees that internal state.
|
||||
* At least as accessible as the record itself. This would mean that if the `record` was `internal`, then internal members would be
|
||||
shown. If the record was `public`, then only `public` members would be shown. This doesn't solve any of our issues with the previous
|
||||
proposal, and adds a behavioral difference between making a type `public` or `internal`.
|
||||
* Only `public` members. This is what the positional constructor will generate by default, and it feels like the most natural state
|
||||
to choose. This ensures that encapsulation isn't violated, and records with lots of `internal` or `private` state that want to
|
||||
display these in a `ToString` are likely not really records anyway. If the user really wants to change this, they can customize it
|
||||
as they choose.
|
||||
|
||||
Conclusion:
|
||||
|
||||
We'll have a generated `ToString`, that will display the public properties and fields of a `record` type. We'll come back with a
|
||||
specific proposal on how this will be implemented at a later point.
|
||||
|
||||
### W-warnings for `T t = default;`
|
||||
|
||||
Just after we initially shipped C# 8, we made a change to warn whenever `default` was assigned to a local or property of type `T`,
|
||||
or when a default was cast to that type. There was no way to silence this warning without using a `!`, so we reverted the change.
|
||||
However, now that we are shipping `T?`, there is an actual syntax that users can write in order to express "this location might
|
||||
be assigned default". We know from experience that this is something that users already do a lot, so making a change here would be
|
||||
pretty breaking, even for the nullable rollout phase of the first year. Further, as we know that if the `!` is the only way to get
|
||||
rid of this warning then we'll get lots of feedback from users, we believe that we can't unconditionally warn here: you can only
|
||||
get rid of the warning in C# 9, so at a minimum it should only be reported in C# 9. We could also put this in a warning wave, and
|
||||
you would only get it when you opt into the warning wave. An important thing to consider with doing this as a warning wave implies
|
||||
that it should be separate error code from 8600: we've previously told people that if they don't want to annotate all their local
|
||||
variables with `?`s, then they should disable that warning and all the rest of the warnings will be actual safety warnings. This
|
||||
would add another warning to that list to disable. We could make it memorable as well, but one of the key aspects in warning waves
|
||||
is that you should be able to opt out of individual warnings if necessary, so users will need to be able to opt out of this new
|
||||
warning without turning off all the existing 8600 warnings. All that being said, there's also a serious backcompat concern here,
|
||||
as introducing the warning when upgrading to C# 9 under the existing error code could cause people to hold off on upgrading at all.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll add a new warning here, under a warning wave.
|
196
meetings/2020/LDM-2020-08-24.md
Normal file
196
meetings/2020/LDM-2020-08-24.md
Normal file
|
@ -0,0 +1,196 @@
|
|||
# C# Language Design Meeting for July 27th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [Warnings on types named `record`](#warnings-on-types-named-record)
|
||||
- [`base` calls on parameterless `record`s](#base-calls-on-parameterless-records)
|
||||
- [Omitting unnecessary synthesized `record` members](#omitting-unnecessary-synthesized-record-members)
|
||||
- [`record` `ToString` behavior review](#record-tostring-behavior-review)
|
||||
- [Behavior of trailing commas](#behavior-of-trailing-commas)
|
||||
- [Handling stack overflows](#handling-stack-overflows)
|
||||
- [Should we omit the implementation of `ToString` on `abstract` records](#should-we-omit-the-implementation-of-tostring-on-abstract-records)
|
||||
- [Should we call `ToString` prior to `StringBuilder.Append` on value types](#should-we-call-tostring-prior-to-stringbuilder.append-on-value-types)
|
||||
- [Should we try and avoid the double-space in an empty record](#should-we-try-and-avoid-the-double-space-in-an-empty-record)
|
||||
- [Should we try and make the typename header print more economic](#should-we-try-and-make-the-typename-header-print-more-economic)
|
||||
- [Reference equality short circuiting](#reference-equality-short-circuiting)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"He was hung up on his principles!"
|
||||
|
||||
"I painted my tower all ivory."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Warnings on types named `record`
|
||||
|
||||
We previously discussed warning in C# 9 when a type is named `record`, as it will need to be escaped in field
|
||||
definitions in order to parse correctly. This is a breaking change being made in C# 9, but we didn't explicitly
|
||||
record the outcome of the warning discussion. We also discussed going further here: we could make lower-cased
|
||||
type names a warning with the next warning wave, under the assumption that lowercase names make good keywords.
|
||||
For example, we could start warning on types named `async` or `var`. Those who want to forbid usage of those
|
||||
features in their codebase have a solution in analyzers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will warn on types named `record` in C# 9. Further discussion will need to happen for lowercase type names
|
||||
in general, as there are globalization considerations, and we feel that disallowing `record` might be a good
|
||||
smoke test for reception to the idea in general.
|
||||
|
||||
### `base` calls on parameterless `record`s
|
||||
|
||||
We have a small hole in records today where this is not legal, and there is no workaround to making it legal:
|
||||
|
||||
```cs
|
||||
record Base(int a);
|
||||
record Derived : Base(1);
|
||||
```
|
||||
|
||||
The spec as written today explicitly forbids this: "It is an error for a record to provide a `record_base`
|
||||
`argument_list` if the `record_declaration` does not contain a `parameter_list`." However, there is good
|
||||
reason to allow this, and while this design may have fallen out of the principles that were set out given
|
||||
that a default constructor for a nominal record is not recognized as a primary constructor, this seems to
|
||||
violate principles around generally making record types smaller. Further, with the spec as written today
|
||||
it's not possible to work around this issue by giving `Derived` an empty parameter list, as parameter lists
|
||||
are not permitted to be empty.
|
||||
|
||||
A solution to this problem is relatively straightforward: we make the default constructor of a nominal record
|
||||
the primary constructor, if no other constructor was provided. This seems to mesh well with the existing rules,
|
||||
and seems to work well with future plans for generalized primary constructors. We could consider 2 variants of
|
||||
this: requiring empty parens on the type declaration in all cases, or only requiring them in cases where the
|
||||
default constructor would otherwise be omitted (if another constructor was added in the `record` definition).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll allow empty parens as a constructor in C# 9. We're also interested in allowing the parens to be omitted
|
||||
when a default constructor would otherwise be emitted, but if this doesn't make it in C# 9 due to time
|
||||
constraints it should be fine to push back to v next.
|
||||
|
||||
### Omitting unnecessary synthesized `record` members
|
||||
|
||||
Some record members might be able to be omitted in certain scenarios if they're unneeded. For example, a
|
||||
`sealed` `record` that derives from `System.Object` would not need an `EqualityContract` property, as it
|
||||
can only have the one contract. We could also simplify the implementation of `ToString` if we know there
|
||||
will be no child-types that need to call `PrintMembers`.
|
||||
|
||||
However, despite leading to simplified emitted code for these types, we're concerned that this will cause
|
||||
other code to become more complicated. We'll need to have more complicated logic in the compiler to handle
|
||||
these cases. Further, source generators and other tools that interact with records programmatically will
|
||||
have to duplicate this logic if they need to interact with record equality or any other portion of code that
|
||||
"simplified" by this mechanism. We further don't see a real concern for metadata bloat in this scenario,
|
||||
as we aren't omitted fields from the record declaration.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
There are likely more downsides than upsides to doing something different here. We'll continue to have this
|
||||
be regular.
|
||||
|
||||
### `record` `ToString` behavior review
|
||||
|
||||
Now that we have a proposal and implementation for `ToString` on record types, we'll go over some standing
|
||||
questions from initial implementation review. Prior art for this area in C# that we found relevant was
|
||||
`ToString` on both tuples and anonymous types.
|
||||
|
||||
#### Behavior of trailing commas
|
||||
|
||||
There is a proposal to simplify the implementation of `ToString` by always printing trailing commas after
|
||||
properties. This would remove the need for some bool tracking flags for whether a comma was printed as
|
||||
part of a parent's `ToString` call. However, no prior art (in either C# or other languages) that we could
|
||||
find does this. Further, it provokes a visceral reaction among team members as feeling unfinished, and that
|
||||
it would be the first bug report we get after shipping it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
No trailing commas.
|
||||
|
||||
#### Handling stack overflows
|
||||
|
||||
There is some concern that `ToString` could overflow if a record type is recursive. While this is also true
|
||||
of equality and hashcode methods on records, there is some concern that `ToString` might be a bit special
|
||||
here, as it's the tool that a developer would use to get a print-out of the record to find the cyclic element
|
||||
in the first place. There's examples in the javascript world of this being a pain point. However, any
|
||||
solution that we come up with for records will have limitations: it wouldn't be able to protect against
|
||||
indirect cycles through properties that are records but not records of the same type, or against cycles
|
||||
in properties that are not records at all. In general, if a user makes a record type with a cyclic data
|
||||
structure, this is a problem they're going to have to confront already in equality and hashcode.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will not attempt any special handling here. We could consider adding a `DebuggerDisplay` with more special,
|
||||
reflective handling at a later point in time if this proves to be a pain point, but we don't think it's
|
||||
worth the cost in regular `ToString` calls.
|
||||
|
||||
#### Quoting/escaping values
|
||||
|
||||
Today, records do not attempt to escape `string`s when printing, which could result in potentially confusing
|
||||
printing. However, we do have concerns here. First, none of our prior art in C# does this escaping. Second,
|
||||
the framework also does not escape `ToString()` like this. Third, there's a question of what _type_ of
|
||||
escaping we should do here. Would we want to print a newlines as the C# version, `\n`, or the VB version?
|
||||
Further, we already have some handling the expression evaluator and debugger for regular strings and
|
||||
anonymous types, to ensure that the display looks good there. Given that this is mainly about ensuring
|
||||
that records appear well-formatted in the debugger, it seems like the correct and language-agnostic approach
|
||||
is to ensure the expression evaluator knows about record types as well.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We won't do any quoting or escaping of string record members. We should make the debugger better here.
|
||||
|
||||
#### Should we omit the implementation of `ToString` on `abstract` records
|
||||
|
||||
We could theoretically omit providing an implementation of this method on `abstract` record types as it
|
||||
will never be called by the derived `ToString` implementation. On the face of it, we cannot think of a
|
||||
scenario that would be particularly helped by including the `ToString`, however we also cannot think of
|
||||
a scenario that would be harmed by including it, and doing so would make the compiler implementation simpler.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Keep it.
|
||||
|
||||
#### Should we call `ToString` prior to `StringBuilder.Append` on value types
|
||||
|
||||
The concern with directly calling `Append` without `ToString` on the value being passed is that for
|
||||
non-primitive struct types, this will cause the struct to be boxed, and the behavior of `Append` is
|
||||
to immediately call `ToString` and pass to the `string` overload.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Do it.
|
||||
|
||||
#### Should we try and avoid the double-space in an empty record
|
||||
|
||||
The implementation currently prints `Person { }` for an empty record. This provokes immediate "unfinished"
|
||||
reactions in the LDT, similar to the trailing commas above.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Should we remove it? YES! YES! YES! YES!
|
||||
|
||||
#### Should we try and make the typename header print more economic
|
||||
|
||||
Today, we call `StringBuilder.Append(nameof(type))` and `StringBuilder.Append("{")` immediately after
|
||||
as 2 separate calls, which is another method call we could drop if we did it all in one. We feel that this
|
||||
is sufficiently outside the language spec as to be a thing a compiler could do if it feels it appropriate.
|
||||
From the compiler perspective, however, it could actually be bad change, as it would increase the size of
|
||||
the string table, which is known to be an issue in some programs, while getting rid of one extra method call
|
||||
in a method not particularly designed to be as fast as possible in the first place.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Up to implementors. However, we don't believe it to be a good idea or worth any effort.
|
||||
|
||||
### Reference equality short circuiting
|
||||
|
||||
Today, we have a slight difference between the equality operators `==`/`!=` and the implementation of the
|
||||
`Equals(record)` instance method, in that the former compare reference equality first, before delegating
|
||||
to the `Equals` method. This ensures that `null` is equal to `null` as a very quick check, and ensures we
|
||||
only have to compare one operand to `null` explicitly in the implementation of the operators. The `Equals`
|
||||
instance member then does not check this condition. However, this means that the performance characteristics
|
||||
of these two members can be very different, with the operators being an order of magnitude faster on even
|
||||
small record types since it has to do potentially many more comparisons. The proposal, therefore, is to
|
||||
check reference equality in the `Equals` instance member as well, as the first element.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will do this. We'll keep the reference equality check in the operators as well to ensure that `null == null`,
|
||||
and add one at the start of the instance method. It does mean that the `==` operators will check reference
|
||||
equality twice, but this is an exceptionally quick check so we don't believe it's a big concern.
|
190
meetings/2020/LDM-2020-09-09.md
Normal file
190
meetings/2020/LDM-2020-09-09.md
Normal file
|
@ -0,0 +1,190 @@
|
|||
# C# Language Design Meeting for September 9th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Triage issues still in C# 9.0 candidate](#triage-issues-still-in-C#-9.0-candidate)
|
||||
2. [Triage issues in C# 10.0 candidate](#Triage-issues-in-C#-10.0-candidate)
|
||||
|
||||
# Quote(s) of the Day
|
||||
|
||||
"Don't even mention I was here."
|
||||
|
||||
"I have lots of strong opinions, I'm just judicious with how I distribute them."
|
||||
|
||||
# Discussion
|
||||
|
||||
## Triage issues still in C# 9.0 candidate
|
||||
|
||||
To start, we updated https://github.com/dotnet/csharplang/blob/master/Language-Version-History.md with the features that have been merged into the compiler for C# 9 at
|
||||
this point, and moved their corresponding issues to the [9.0 milestone](https://github.com/dotnet/csharplang/milestone/18).
|
||||
|
||||
### Null propagation expression does not allow `!` as a nested operator form
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
We did not adequately prioritize this given the urgency of the breaking change. We need to attempt to get this into C# 9 or we may end up being stuck with this behavior
|
||||
given that the nullable rollout period is ending, or we'd need to consider taking what could be a large breaking change. We'll attempt to throw and catch this hail mary,
|
||||
and it will stay in C# 9.0 candidate for now.
|
||||
|
||||
### [Proposal] Improve overload resolution for delegate compatibility
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3277
|
||||
|
||||
We had hoped to get this done as a refactoring while implementing function pointers, but did not end up getting it in. This is a good candidate for community contribution
|
||||
as it can be implemented and tested in a single PR, so we'll move this to any time.
|
||||
|
||||
### Interface static method, properties and event should ignore variance
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3275
|
||||
|
||||
This is implemented, just waiting on infrastructure to move forward to a runtime where it can be tested. Will be in C# 9 as soon as that happens, and is staying in 9.0
|
||||
candidate.
|
||||
|
||||
### Records-related issues
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3226
|
||||
https://github.com/dotnet/csharplang/issues/3213
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
These are all records related issues. We didn't get all of what we want to accomplish in this space in C# 9, so we need to break the still-to-be-done parts of these
|
||||
proposals out into separate issues for 10 or later and close/move these.
|
||||
|
||||
### Primary Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2691
|
||||
|
||||
We didn't managed to break this out of records in a satisfactory manner for 9, but we would like to get this in for 10. Retriaged to 10.0 Candidate.
|
||||
|
||||
### Proposal: Target typed null coalescing (`??`) expression
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2473
|
||||
|
||||
When attempting to spec and implement this, we found that it doesn't actually work well in practice, as `??` has a number of complicated rules around type resolution
|
||||
already and what part should be target-typed is confusing. We believe that, given the investigation, we won't be moving this one forward, and it moves to the Likely
|
||||
Never milestone.
|
||||
|
||||
### Champion: Simplified parameter null validation code
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2145
|
||||
|
||||
We didn't quite get the code gen for this one finished up. Moving to 10.0 candidate.
|
||||
|
||||
### Champion: relax ordering constraints around `ref` and `partial` modifiers on type declarations
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/946
|
||||
|
||||
We had put this in 9.0 because we thought it would be required for `data` classes. That did not end up being the case, and we don't have high priority for it otherwise.
|
||||
We'd accept a contribution if a community member wanted to loosen the restriction, thus moved to Any Time.
|
||||
|
||||
### Champion "Nullable-enhanced common type"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/33
|
||||
https://github.com/dotnet/csharplang/issues/881
|
||||
|
||||
We said when doing additional target-typing work that we either had to do these now, or it would be very complicated to implement later without avoiding breaking changes.
|
||||
Given that the target-typing we added more generally addresses this in most scenarios, we don't believe that the additional "break glass in case of emergency" bar is
|
||||
met with these, and they are moved to Likely Never.
|
||||
|
||||
### Proposal: improvements to nullable reference types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3297
|
||||
|
||||
This is part of the ongoing nullable improvements list. We will split the parts that have not been implemented out into a separate list, and then move this to 9.0 with
|
||||
just the things actually implemented for this release.
|
||||
|
||||
## Triage issues in C# 10.0 candidate
|
||||
|
||||
We will be moving issues from the 10.0 candidate bucket to the 10.0 Working Set bucket during triage. Issues in the 10.0 Working Set bucket are issues that we have decided
|
||||
to devote some design time towards during the upcoming 10.0 timeframe, but not all of the issues triaged into this working set will actually make it into the 10.0 release.
|
||||
|
||||
### Proposal: Exponentiation operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2585
|
||||
|
||||
We don't feel that this is a major priority for the language currently. It is a relatively small amount of work, however. If a community member wants to add a proposal
|
||||
for a specific symbol to use for the operator and a precedence for that symbol, we'd be happy to consider it at that point. Moved to Any Time.
|
||||
|
||||
### Champion "Type Classes (aka Concepts, Structural Generic Constraints)"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/110
|
||||
|
||||
We feel that issues in improving the type system, such as type classes, roles, statics in interfaces, etc, are the next big area of investment for the language. While
|
||||
we may not end up seeing the results of these investments in the 10.0 timeframe, we need to start designing them now or we'll never see them at all. As such, we are
|
||||
labeling this issue as "Long lead" to indicate that there is a lot of design work to come before and implementation can even start to happen. Moved to 10.0 Working Set.
|
||||
|
||||
### generic constraint: where T : ref struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1148
|
||||
|
||||
This is related to a forthcoming proposal on ref fields, as well as to the aforementioned type system improvements we want to make. To really be useful beyond allowing
|
||||
`Span<Span<T>>`, ref structs need to be able to implement interfaces, and so this will be considered in tandem with them. Moved to 10.0 Working Set.
|
||||
|
||||
### Champion "CallerArgumentExpression"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/287
|
||||
|
||||
While this could be considered Any Time, we already have most of an implementation and would like to get it in for C# 10, so we are moving it to 10.0 Working Set.
|
||||
|
||||
### Champion "Allow Generic Attributes"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/124
|
||||
|
||||
A community member is contributing this feature, and while we don't think there is any design work left to do from the language side there's still a bit of work on the
|
||||
implementation itself. We'll move this to 10.0 Working Set, as opposed to Any Time, to ensure that we give priority if there does end up being any language work to do.
|
||||
|
||||
### C# Feature Request: Allow value tuple deconstruction with default keyword
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1358
|
||||
|
||||
This is a small quality of life change that we already have a PR for. Let's get it merged, and move this issue to 10.0 Working Set.
|
||||
|
||||
### Expression Blocks/Switch Expression and Statement Enhancements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3086
|
||||
https://github.com/dotnet/csharplang/issues/3038
|
||||
https://github.com/dotnet/csharplang/issues/3087
|
||||
https://github.com/dotnet/csharplang/issues/2632
|
||||
|
||||
We know that we want to make some improvements here. We need to narrow down on a concrete proposal as we have a lot of different scopes/syntaxes currently, and then we
|
||||
can proceed with implementation. Move to 10.0 Working Set.
|
||||
|
||||
### Champion: Name lookup for simple name when target type known
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2926
|
||||
|
||||
We consider this a somewhat essential necessary feature for discriminated unions, so this should be considered in concert with that. Move to 10.0 Working Set.
|
||||
|
||||
### Proposal: "Closed" type hierarchies
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/485
|
||||
|
||||
This needs to be designed in concert with discriminated unions, so we'll move it to 10.0 Working Set.
|
||||
|
||||
### Championed: Target-typed implicit array creation expression `new[]`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2701
|
||||
|
||||
This needs some design work around either making it a generalized feature for type parameter-based inference or if it specializes for the interfaces that array implements
|
||||
specifically. We don't feel that this particularly pressing, so we're moving it to X.0 unless we hear a need to add it to align with other .NET priorities.
|
||||
|
||||
### Champion: `base(T)` phase two
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2337
|
||||
|
||||
While this was part of the initial feature set for DIMs, we don't hear any particular customer complaints for this feature. We'll move it to X.0 until such time as we
|
||||
hear customer asks for it.
|
||||
|
||||
### Champion "defer statement"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1398
|
||||
|
||||
LDM was somewhat negative on this feature. There are times when you'd want to pair code to run at the end of a block/method return, but defer has invisible consequences
|
||||
here because it would have to use try/finally. This give defer a penalty that would prevent it from being used in many of the places we'd otherwise want to use it ourselves.
|
||||
Additionally, there is significant negative community sentiment about this feature, much more than we usually get for participation on any particular csharplang issue. As a
|
||||
result, we are moving this feature to Likely Never, and if we see similar significant outcry to the rejection we can reconsider this.
|
||||
|
||||
### Five ideas for improving working with fixed buffers
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1502
|
||||
|
||||
This will be part of the previously-mentioned forthcoming ref fields proposal. Move it to 10.0 Working Set.
|
111
meetings/2020/LDM-2020-09-14.md
Normal file
111
meetings/2020/LDM-2020-09-14.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
# C# Language Design Meeting for September 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Partial method signature matching](#partial-method-signature-matching)
|
||||
2. [Null-conditional handling of the nullable suppression operator](#null-conditional-handling-of-the-nullable-suppression-operator)
|
||||
3. [Annotating IEnumerable.Cast](#annotating-ienumerable.cast)
|
||||
4. [Nullability warnings in user-written record code](#nullability-warnings-in-user-written-record-code)
|
||||
5. [Tuple deconstruction mixed assignment and declaration](#tuple-deconstruction-mixed-assignment-and-declaration)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Now with warning waves it's a foam-covered baseball bat to hit people with"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Partial method signature matching
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/45519
|
||||
|
||||
There is a question about what amount of signature matching is required for method signatures, both as part of the expanded partial methods in C# 9
|
||||
and for the new `nint` feature in C# 9. Currently, our rules around what has to match and what does not are confusing: tuple names must match,
|
||||
`dynamic`/`object` do not have to match, we warn when there are unsafe nullability conversions, and other differences are allowed (including parameter
|
||||
names). We could lean on a warning wave here and make our implementation more consistent with the following general rules:
|
||||
|
||||
1. If there are differences in the CLR signature, we error (as we cannot emit code at all!)
|
||||
2. If there are differences in the syntactic signature, we warn (even for safe nullability changes).
|
||||
|
||||
While we like this proposal in general, we have a couple of concerns around compatibility. Tuple names erroring is existing behavior, and if we loosen
|
||||
that to a general warning that would need to be gated behind language version, as you could write code with a newer compiler in C# 8 mode that does not
|
||||
compile with the C# 8 compiler. This complicates implementation, and to make it simple we lean towards just leaving tuple name differences as an error.
|
||||
We also want to make sure that nullability is able to differ by obliviousness: a common case for generators is that either the original signature or the
|
||||
implementation will be unaware of nullable, and we don't want to break this scenario such that either both the user and generator must be nullable aware,
|
||||
or the must both be nullable unaware.
|
||||
|
||||
We also considered an extension to these rules where we make the new rules always apply to the new enhanced partial methods, regardless of warning level.
|
||||
However, we believe that this would result in a complicated user experience and would make the mental model harder to understand.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
1. We will keep the existing error that tuple names must match.
|
||||
2. We will keep the existing warnings about unsafe nullability differences.
|
||||
3. We will add a new warning wave warning for all other syntactic differences between partial method implementations.
|
||||
a. This includes differences like parameter names and `dynamic`/`object`
|
||||
b. This includes nullability differences where both contexts are nullable enabled, even if the difference is supposedly "safe" (accepting `null`
|
||||
where it is not accepted today).
|
||||
c. If nullability differs by enabled state (one part is enabled, the other part is disabled), this will be allowed without warning.
|
||||
|
||||
### Null-conditional handling of the nullable suppression operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
This is a spec bug that shipped with C# 8, where the `!` operator does not behave as a user would expect. Members of the LDT believe that this is broken
|
||||
on the same level as the `for` iterator variable behavior that was changed in C# 5, and we believe that we should take a similar breaking change to fix
|
||||
the behavior here. We have made a grammatical proposal for adjusting how null-conditional statements are parsed, and there was general agreement that this
|
||||
proposal is where we want to go. The only comment is that `null_conditional_operations_no_suppression` should be renamed to avoid confusion, as there can
|
||||
be a null suppression inside the term, just not at the end. A better name would be `null_conditional_operations_no_final_suppression`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted, with the above rename. Will get a compiler developer assigned to implement this ASAP.
|
||||
|
||||
### Annotating IEnumerable.Cast
|
||||
|
||||
https://github.com/dotnet/runtime/issues/40518
|
||||
|
||||
In .NET 5, the `Cast<TResult>` method was annotated on the return to return `IEnumerable<TResult?>`, which means that regardless of whether the input
|
||||
enumerable can contain `null` elements, the returned enumerable would be considered to contain `null`s. This resulted in some spurious warnings when
|
||||
upgrading roslyn to use a newer version of .NET 5. However, the C# in general lacks the ability to properly annotate this method for a combination of
|
||||
reasons:
|
||||
|
||||
1. There is no way to express that the nullability of one type parameter depends on the nullability of another type parameter.
|
||||
2. Even if there was a way to express 1, `Cast` is an extension method on `IEnumerable`, not `IEnumerable<T>`, because C# does not have partial type
|
||||
inference to make writing code in this scenario better.
|
||||
|
||||
Given this, we have a few options:
|
||||
|
||||
1. Leave the method as is, and possibly enhance the compiler/language to know about this particular method. This is analogous to the changes we're
|
||||
considering with `Where`, but it feels like a bad solution as it's not generalizable.
|
||||
2. Make the method return `TResult`, unannotated. The issue with this is that it effectively means the method might actually lie: there is no way to
|
||||
ensure that the method actually returns a non-null result if a non-null `TResult` is provided as a type, given that nullability is erased in the
|
||||
implementation. We're concerned that this could make the docs appear to lie, which we think would also give a bad experience.
|
||||
3. Convert `Cast` back to being unannotated. This seems to be compromise that both sides can agree on: analyzers can flag use of the unannotated API
|
||||
to help users, and spurious warnings get suppressed. It also matches the behavior of `IEnumerator.Current`, and means that the behavior of `foreach`
|
||||
loops over such a list behave in a consistent manner.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The BCL will make `Cast` and a few related APIs an oblivious API.
|
||||
|
||||
### Nullability warnings in user-written record code
|
||||
|
||||
The question here is on whether we should warn users when manually-implemented methods and properties for well-known members in a `record` should warn
|
||||
when nullability is different. For example, if their `Equals(R other)` does not accept `null`. There was no debate on this.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
we'll check user-defined `EqualityContract`, `Equals`, `Deconstruct`, ... methods on records for nullability safety issues in their declaration. For
|
||||
example, `EqualityContract` should not return Type?.
|
||||
|
||||
### Tuple deconstruction mixed assignment and declaration
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/125
|
||||
|
||||
We've discussed this feature in the past (https://github.com/dotnet/csharplang/blob/master/meetings/2016/LDM-2016-11-30.md#mixed-deconstruction), and
|
||||
we liked it then but didn't think it would fit into C# 7. It's been in Any Time since, and now we have a community PR. We have no concerns with
|
||||
moving forward with the feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Let's try and get this into C# 10.
|
66
meetings/2020/LDM-2020-09-16.md
Normal file
66
meetings/2020/LDM-2020-09-16.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
# C# Language Design Meeting for September 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required Properties](#required-properties)
|
||||
2. [Triage](#triage)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "It's my version of thanks Obama, thanks C#"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
[Presentation](./Required_Properties_2020_09_16.pdf)
|
||||
|
||||
Most of the time today was used hearing a presentation about a proposal for required properties. This presentation was intended
|
||||
as a high-level overview of the issue required properties are attempting to solve, and to illustrate a few approaches that could
|
||||
be taken for implementing these. Notes were not taken to the slide content itself, as the slides are linked above.
|
||||
|
||||
Overall, the LDM is a bit leary about the implementation complexity of runtime-verification methods. One approach that wasn't on
|
||||
the slides, but is a possible approach, is introducing validators and letting type authors do the verification themselves. This
|
||||
may end up resulting in type authors conflating nullability and requiredness for most simple implementations, but for the common
|
||||
scenarios this may be just fine. We were unable to find just one implementation strategy that stuck out as "this is the right thing
|
||||
to do." Instead, each implementation strategy had a set of tradeoffs, particularly around the implementation complexity of tracking
|
||||
whether a property has been initialized.
|
||||
|
||||
One thing that we noted from the presentation is that any method of introducing required properties is going to mean that types with
|
||||
these contracts on their empty constructors will not be able to be used in generic scenarios `where T : new()`, as the empty constructor
|
||||
is required to set some amount of properties. The presentation also did not cover how to do additive or subtractive contracts: ie, a
|
||||
future factory method `MakeAMads` might want to specify "Everything from the normal `Person` constructor except `FirstName`", whereas
|
||||
a copy constructor would want to specify "You don't have to initialize anything". Some work has started on designing this scenario,
|
||||
but it needs some more work before it's in a state to get meaningful feedback.
|
||||
|
||||
A read of the room found that most of the LDM was leaning towards compile-time only validation of required properties. It does mean
|
||||
that upgrading binary versions without recompiling can leave things in an invalid state, but the implementation strategies for
|
||||
getting actual runtime breaks are very complex and we're not sure they're worth the tradeoff. The next step in design will be to
|
||||
come back with a more concrete syntax proposal for how required properties will be stated, and how constructors will declare their
|
||||
contracts.
|
||||
|
||||
### Triage
|
||||
|
||||
We only had time left to triage 1 issue:
|
||||
|
||||
#### Proposal: Permit trailing commas in method signatures and invocations
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/391
|
||||
|
||||
This is a longstanding friction point in the language that has mostly positive sentiment on csharplang. We're also fairly confident
|
||||
that it wouldn't break any possible language features other than omitted arguments, which the LDM is not particularly interested
|
||||
in pursuing. That being said, the general gut reaction of most LDM members is something along the lines of "This looks wrong." We
|
||||
certainly acknowledge that, despite some syntactic misgivings, this can be a very useful feature, particularly for source generators
|
||||
and refactorings, as well as for just simply reordering parameters in a method call or definition. There are a couple of open issues
|
||||
we'd need to resolve as well:
|
||||
|
||||
* What would happen with param arrays?
|
||||
* Would tuple declarations be allowed to have trailing commas? This proposal actually seems like a natural way to allow oneples into
|
||||
the language, in a similar style to Python, where the oneple must have a trailing comma.
|
||||
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage in X.0. We think this feature has a place in C#, but we don't think it will make the bar for what we're interested in with C# 10.
|
37
meetings/2020/LDM-2020-09-23.md
Normal file
37
meetings/2020/LDM-2020-09-23.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# C# Language Design Meeting for September 23rd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. General improvements to the `struct` experience
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "This makes C# developers queasy. Refs can never be null! The runtime says that's not true, the C# type system is a lie."
|
||||
|
||||
## Discussion
|
||||
|
||||
Most of this session was dedicated to explaining the proposal itself, which can be found [here](https://github.com/dotnet/csharplang/blob/master/proposals/low-level-struct-improvements.md).
|
||||
We covered the section `Provide ref fields` in full, but did not get beyond that in this session. What follows are some noted comments on the
|
||||
proposal, as we did not have long discussions about the merits of the feature itself.
|
||||
|
||||
* The general driving principles of this proposal are to ease friction points in the language around structs. Today we have `Span<T>`, which is
|
||||
a very useful type, but it uses a magic BCL-internal type called `ByReference<T>`. Span uses it to great effect, but because there are no compile
|
||||
rules around its safety we can't expose it publicly. This leads to 3rd parties using reflection to get it, which just results in badness all
|
||||
around. We'd like to allow the semantics to be fully specifiable in C# and enable it not just for the BCL, but for all types of structs. The LDM
|
||||
generally agrees with this goal.
|
||||
* There will need to be some metadata tool updates. In order for safe-to-escape analysis to work, we will have to ensure that all `ref` fields,
|
||||
even private fields, appear in reference assemblies and similar locations to ensure that the compiler can correctly determine lifetimes.
|
||||
* The rules around returning a ref to a ref parameter are not especially intuitive. We acknowledge that they are necessary given the lifetime
|
||||
rules today with methods returning a `Span`, but we could introduce a `CapturesAttribute` or something similar to indicate that a `ref` parameter
|
||||
is captured by the method, and thus allow passing it directly as a `ref` to `new Span<T>`.
|
||||
* There is a workaround for this behavior: instead of taking a `ref T` in the constructor, take a `Span<T>`, which will ensure that all the
|
||||
lifetimes line up. While this workaround is viable, we're somewhat worried it won't be straightforward enough of a solution.
|
||||
* Allowing `ref` assignment after the fact could be done in a later version of C#. It's a good deal of work in the compiler (likely an entirely
|
||||
new flow analysis pass) to correctly update the lifetimes, and we're not yet certain that the scenario is worth the effort. If this proves to be
|
||||
a friction point for users, we can revisit.
|
||||
* One scenario this spec does not consider is `ref` assignment in an object initializer, which is still part of the object construction phase.
|
||||
This should be allowable, and we need to update the draft specification to address this case.
|
||||
* `ref null` is going to be an annoying problem. Given that you can `default` structs in any of a number of ways, at some point it will be possible
|
||||
to observe a `default` ref struct that has a `null` `ref`. While the runtime does have an `Unsafe.IsNullRef` helper method, it feels unnatural that
|
||||
code that is entirely safe C# should have to use a method from the `Unsafe` class. Further, these newly-observable `null` `ref`s will will end up
|
||||
everywhere, in much the same way that `null` ends up everywhere. We may need to think more about this problem.
|
207
meetings/2020/LDM-2020-09-28.md
Normal file
207
meetings/2020/LDM-2020-09-28.md
Normal file
|
@ -0,0 +1,207 @@
|
|||
# C# Language Design Meeting for September 28th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Warning on `double.NaN`](#warning-on-double.nan)
|
||||
2. [Triage](#triage)
|
||||
1. [Proposal: more improvements to nullable reference types](#proposal-more-improvements-to-nullable-reference-types)
|
||||
2. [Proposal: Required Properties](#proposal-required-properties)
|
||||
3. [Proposal: Extend with expression to anonymous type](#proposal-extend-with-expression-to-anonymous-type)
|
||||
4. [Proposal: Shebang (#!) Support](#proposal-shebang--support)
|
||||
5. [Proposal: List Patterns](#proposal-list-patterns)
|
||||
6. [Proposal: Add ability to declare global usings for namespaces, types and aliases by using a command line switch](#proposal-add-ability-to-declare-global-usings-for-namespaces-types-and-aliases-by-using-a-command-line-switch)
|
||||
7. [Proposal: "Closed" enum types](#proposal-closed-enum-types)
|
||||
8. [Top-level functions](#top-level-functions)
|
||||
9. [Primary Constructors](#primary-constructors)
|
||||
10. [Champion: Simplified parameter null validation code](#champion-simplified-parameter-null-validation-code)
|
||||
11. [Proposal: Support generics and generic type parameters in aliases](#proposal-support-generics-and-generic-type-parameters-in-aliases)
|
||||
12. [Support for method parameter names in nameof](#support-for-method-parameter-names-in-nameof)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "On a rational basis I have nothing against this"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Warning on `double.NaN`
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/15936
|
||||
|
||||
We have an existing FxCop warning (CA2242) for invalid comparisons to `double.NaN`. We could consider bringing
|
||||
that warning into the compiler itself as in a warning wave. However, as this analyzer is now shipped with the
|
||||
.NET 5 SDK, is on by default, and deals more with API usage than with the language itself, it would also be fine
|
||||
to leave it where it is.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Rejected. Leave the warning where it exists today.
|
||||
|
||||
### Triage
|
||||
|
||||
Today, we got through half the remaining issues in the C# 10.0 Candidate milestone
|
||||
|
||||
#### Proposal: more improvements to nullable reference types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3868
|
||||
|
||||
We have a few open topics in improvements to nullable reference types for the C# 10.0 timeframe. We're currently
|
||||
tracking LINQ improvements, Task-like type covariance, and uninitialized fields and constructors. This last point
|
||||
will likely be handled in conjunction with the proposals for required properties and initialization debt. The
|
||||
first two can be broken out to specific issues for the 10.0 timeframe.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
File separate issues for the first two bullets, and triage into the 10.0 working set.
|
||||
|
||||
#### Proposal: Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
We've already started talking about this one.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into the 10.0 working set.
|
||||
|
||||
#### Proposal: Extend with expression to anonymous type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3530
|
||||
|
||||
This proposal extends anonymous types to allow `with` expressions to change them, which we like a lot. In a way,
|
||||
this extension makes anonymous types practically just anonymous _record_ types, as they have the rest of the properties
|
||||
of a record already: value equality, ToString, etc. It also fits in well with the idea of generally extending `with`
|
||||
expressions to be more broadly applicable. Since anonymous types cannot be exposed as types in a public API, generating
|
||||
new `init` members and the `with` clone method is a non-breaking change. New compilations can take advantage of the
|
||||
features, and old compilations don't get affected by them.
|
||||
|
||||
As part of discussing this issue, we hit upon the idea of additionally allowing `new { }` to be target-typed. If a `new`
|
||||
expression that did not have `()` is assigned to something that matches its shape (such as a record with the correct)
|
||||
property names, we could just allow that new expression to be treated as the target-type constructor, rather than as
|
||||
creating a new anonymous type. If the target-type is `object` or `dynamic`, it will still result in an anonymous object
|
||||
being created, and there may be some tricks to figuring out generic inference, but we think it might be a path forward
|
||||
towards making target-typed new more regular with the rest of the language (a complaint we have already heard). A future
|
||||
proposal for that will be filed.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into C# 10.0 working set for consideration with the rest of the `with` extensions.
|
||||
|
||||
#### Proposal: Shebang (#!) Support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3507
|
||||
|
||||
While could eventually be an interesting proposal, the tooling is not there currently, and we feel the discussion around
|
||||
developer ergonomics in .NET 6 will shape our discussions in this area.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into X.0, and if the .NET tooling looks to add `dotnet run csfile`, we can consider again at that point.
|
||||
|
||||
#### Proposal: List Patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3435
|
||||
|
||||
We like continuing to enhance the pattern support in C#, and are in general positive about this proposal. However, the
|
||||
somewhat-fractured nature of .NET here works to our detriment, not just in the `Count` vs `Length` property names, but
|
||||
in the general collection type sense. It would be nice to support `IEnumerable`, for example, which does not meet the
|
||||
definitions set out in the proposal. Another consideration would be dictionary types: we don't have support for a
|
||||
dictionary initializers specifically today, so having a decomposition step without a composition step would be odd, but
|
||||
they would be a useful pattern nontheless. We also would like to see if we can find a way to make the syntax use braces
|
||||
rather than square brackets to mirror collection initializers, though that will be difficult due to the empty property
|
||||
pattern.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll spend design time on this in the C# 10 timeframe, though it may not make the 10.0 release itself. Triaged into the
|
||||
10.0 working set.
|
||||
|
||||
#### Proposal: Add ability to declare global usings for namespaces, types and aliases by using a command line switch
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
This is, in many ways, a language-defining issue. The proposal itself is small, but it reflects the LDT's thinking of
|
||||
future C# directions as a whole. It is also very divisive, both in the LDT and in the greater community, with a small
|
||||
majority (both in the LDT and in the csharplang community) in favor of the feature. It introduces a level of magic to
|
||||
the language that has been somewhat resisted in the past. Additionally, there is concern that there is no one set of
|
||||
"base usings" that should be automatically included in files: a console app might only want to include `System`, while
|
||||
an ASP.NET app might want to include a bunch of namespaces for various routing properties, controllers, and the like.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll discuss this more in the 10.0 timeframe. There could be interactions around the theme of developer QoL in the .NET
|
||||
6 timeframe.
|
||||
|
||||
#### Proposal: "Closed" enum types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3179
|
||||
|
||||
This proposal is linked to discriminated unions, in that it's a another type of closed hierarchy that C# does not support
|
||||
today.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set, to be discussed with DU's and closed type hierarchies in general.
|
||||
|
||||
#### Top-level functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
This is the follow-up work from C# 9 that we did not conclude in the initial push for top-level statements in C#,
|
||||
namely in allowing globally-accessible functions in C# to float at the top level without a containing class. One
|
||||
concern with generalizing this feature is that we have 20 years of BCL design that does not account for top-level
|
||||
functions, in addition to other well-known and used libraries. For consistency, these libraries would likely not
|
||||
use this new feature, which would relegate this feature to minimal usage. While many LDT members like the feature
|
||||
in general, and would likely introduce it if we were redesigning C# from the ground up, we don't believe that the
|
||||
feature belong in the C# we have today. We will continue to investigate whether top-level functions defined today
|
||||
(which are local functions to the implicit `Main` method) should be callable from within the current file, in order
|
||||
to have better compat with CSX. However, the overall feature is rejected.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
#### Primary constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2691
|
||||
|
||||
We have a proposal for this, and we are mostly in agreement that we should see this through.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set.
|
||||
|
||||
#### Champion: Simplified parameter null validation code
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2145
|
||||
|
||||
We have an implementation of this mostly ready. It was done in such as way as to be usable by the BCL, and will
|
||||
potentially save 7K+ lines of code there. Let's get it in.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set.
|
||||
|
||||
#### Proposal: Support generics and generic type parameters in aliases
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1239
|
||||
|
||||
We like the idea of improving aliases in general, and this could come into play when we talk about the previously-mentioned
|
||||
global usings. There are multiple possible flavors here though: there's simple aliases like we have today, that are
|
||||
freely convertible back to the underlying type, and then there are true opaque aliases that are not freely convertible
|
||||
back to the underlying type. Ideally, these latter aliases would be zero cost, which will likely require some work in
|
||||
conjunction with the runtime. Additionally, while we like the idea of making improvements here, we have a lot on the C# 10
|
||||
plate currently, and think it would fit in better with the type enhancements we hope to make after 10.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into X.0.
|
||||
|
||||
#### Support for method parameter names in nameof
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/373
|
||||
|
||||
We like this, and have the start of an implementation.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into 10.0 working set.
|
134
meetings/2020/LDM-2020-09-30.md
Normal file
134
meetings/2020/LDM-2020-09-30.md
Normal file
|
@ -0,0 +1,134 @@
|
|||
# C# Language Design Meeting for September 30th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `record structs`
|
||||
1. [`struct` equality](#struct-equality)
|
||||
2. [`with` expressions](#with-expressions)
|
||||
3. [Primary constructors and `data` properties](#primary-constructors-and-data-properties)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "... our plan of record"
|
||||
- "haha badum-tish"
|
||||
|
||||
## Discussion
|
||||
|
||||
Our conversation today focused on bringing records features to structs, and specifically what parts should apply
|
||||
to _all_ structs, what should apply to theoretical `record` structs (if that's even a concept we should have), and
|
||||
what should not apply to structs at all, regardless of whether it's a `record` or not. The table we looked at is
|
||||
below, followed by detailed summaries of our conversations on each area. We did not come to a confirmed conclusion
|
||||
on primary constructors/data properties this meeting, but we do have a general room lean, which has been recorded.
|
||||
|
||||
|Feature |All structs |Record structs|No structs|
|
||||
|----------------------------------|--------------|--------------|----------|
|
||||
|Equality |--------------|--------------|----------|
|
||||
|- basic value equality | X (existing) | | |
|
||||
|- == and strongly-typed `Equals()`| | X | |
|
||||
|- `IEquatable<T>` | | X | |
|
||||
|- Customized value equality | X | | |
|
||||
|`with` |--------------|--------------|----------|
|
||||
|- General support | X | | |
|
||||
|- Customized copy | |explicit error| X |
|
||||
|- `with`ability abstraction | | ? | |
|
||||
|Primary constructors |--------------|--------------|----------|
|
||||
|- Mutability by default | | | leaning |
|
||||
|- Public properties | | leaning | |
|
||||
|- Deconstruction | | leaning | |
|
||||
|Data properties |--------------|--------------|----------|
|
||||
|- Mutability by default | | | leaning |
|
||||
|- Public properties | | leaning | |
|
||||
|
||||
### `struct` equality
|
||||
|
||||
`record`s in C# 9 are very `struct` inspired in their value equality: all fields in `record` a and b are compared
|
||||
for equality, and if they are all equal, then a and b are also equal. This is the default behavior for all `struct`
|
||||
types in C# today, if an `Equals` implementation is not provided. However, this default implementation is somewhat
|
||||
slow, as it uses reflection. We've talked in the past about potentially generating an `Equals` implementation for
|
||||
all struct types that would have better performance. However, we are definitely very concerned about potential size
|
||||
bloat for doing this, particularly around interop types. Given those concerns, we don't think we can generate such
|
||||
methods for all struct types. We then considered whether hypothetical `record` structs should get this implementation.
|
||||
However, generating a good implementation of equality for structs almost seems like it's not a C# language issue at
|
||||
all. More than just C# runs on the CLR, and it would be a shame if there was incentive to use a particular language
|
||||
because it generates a better equality method. Further, since we can't do this for all `struct` types, it means we
|
||||
would inevitably have to educate users that "`record struct`s generate better code for equality, so you may just
|
||||
want to make your `struct` a `record` for that reason alone", which isn't great either. Given that, we'd rather work
|
||||
with the runtime team to make the automatic `Equals` method better, which will benefit not just all C# structs,
|
||||
but all `struct` types from all languages that run on the CLR.
|
||||
|
||||
Next, we looked at whether we should expose new equality operators and strongly-typed `Equals` methods on `struct`
|
||||
types, as well as implementing `IEquatable<T>`. We again came to the conclusion that, for all existing `struct`
|
||||
types, it would be too costly in metadata (and a potential breaking change for exposing a strongly-typed `Equals`
|
||||
method or `IEquatable<T>`) to do this for all types. However, we do think that a gesture for opting into this
|
||||
generation would be useful. Given that, we considered whether it was useful to have these be more granular gestures,
|
||||
ie if a type could just opt-in to generating `IEquatable<T>` without the equality features. For these scenarios, we
|
||||
feel that the need just isn't there, and that it should be an all-or-nothing opt-in.
|
||||
|
||||
Finally on this topic, we considered customized `Equals` implementations. This is a fairly simple topic: all structs
|
||||
support customizing their definition of `Equals` today, and will continue to do so in the future.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
All structs will continue to use the runtime-generated `Equals` method if none is provided. Making a `struct` a
|
||||
`record` will be a way to opt-in to new surface area that uses this functionality. We will work with the runtime
|
||||
team to hopefully improve the implementation of the generated `Equals` methods in future versions of .NET.
|
||||
|
||||
### `with` expressions
|
||||
|
||||
We considered whether all structs should be copyable via a `with` expression, or just some subset of them. On the
|
||||
surface, this seems a simple question: all structs are copyable today, and we even have a dedicated CIL instruction
|
||||
for this: `dup`. It seems trivial to enable this for any location where we know the type is a `struct` type, and
|
||||
just emit the `dup` instruction. Where this becomes a more interesting question, though, is in the intersection
|
||||
between all structs and any potential for customization of the copy behavior. We have plans to enable `with`
|
||||
as a general pattern that any class can implement through some mechanism, and if structs can customize that behavior
|
||||
it means that a struct substituted for a generic type `where T : struct` will behave incorrectly if that behavior
|
||||
was customized. Additionally, if we extend `with`ability as a pattern and allow it to be expressed via some kind of
|
||||
interface method, would structs be able to implement that method? Or would it get an automatic implementation of
|
||||
that method?
|
||||
|
||||
An important note for structs is that, no matter what we do here with respect to `with`, structs are fundamentally
|
||||
different than classes as they're _already_ copied all the time. Unless someone is ensuring that they always pass
|
||||
a struct around via `ref`, the compiler is going to be emitting `dup`s all the time. While we could design a new
|
||||
runtime intrinsic to call either `dup` or the struct's clone method if it exists, struct cloning behavior has long-
|
||||
established semantics that we think users will continue to expect.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
All `struct` types should be implicitly `with`able. No `struct` types should be able to customize their `with`
|
||||
behavior. Depending on how we implement general `with` abstractions, `record` structs might be able to opt-in to them,
|
||||
but will still be unable to customize the behavior of that abstraction.
|
||||
|
||||
### Primary constructors and `data` properties
|
||||
|
||||
Finally today, we considered the interactions of primary constructors, data properties, and structs. There are two
|
||||
general ideas here:
|
||||
|
||||
1. `struct` primary constructors should mean the same thing as `class` primary constructors (with whatever behavior
|
||||
we define later in this design cycle), and `record struct` primary constructors should mean the same thing as
|
||||
`record` primary constructors (public init-only properties), or
|
||||
2. `record struct` primary constructors should mean public, mutable fields.
|
||||
|
||||
Option 1 would provide a symmetry between record structs and record class types, while option 2 would provide a
|
||||
symmetry between record structs and tuple types. In a sense, a record struct would just become a strongly-named tuple
|
||||
type, and have all the same behaviors as a standard tuple type. You could then opt a record struct into being
|
||||
immutable by declaring the whole type `readonly`, or declaring the individual parameters `readonly`. For example:
|
||||
|
||||
```cs
|
||||
// Public, mutable fields named A and B
|
||||
record struct R1(int A, int B);
|
||||
|
||||
// Public, readonly fields named A and B
|
||||
readonly record struct R2(int A, int B);
|
||||
```
|
||||
|
||||
A key point in the mutability question for structs is that mutability in a struct type is nowhere near as bad as
|
||||
mutability in a reference type. It can't be mutated when it's the key of a dictionary, for example, and unless
|
||||
refs to the struct are being passed around the user is always in control of the struct. Further, if a ref is passed
|
||||
and it is saved elsewhere, that's a copy, and mutation to that copy doesn't affect the original. As always, we also
|
||||
have easy syntax to make something readonly in C#, while not having an easy syntax for making it mutable. On the other
|
||||
hand, the shortest syntax in a class record type is to create an immutable property, and it might be confusing if
|
||||
we had differing behaviors between record classes and record structs.
|
||||
|
||||
We did not come to a conclusion on this topic today. A general read of the room has a _slight_ lean towards keeping
|
||||
the behavior consistent with record classes, but a number of members are undecided as there are good arguments in
|
||||
both directions. We will revisit this topic in a future meeting after having some time to mull over the options here.
|
71
meetings/2020/LDM-2020-10-05.md
Normal file
71
meetings/2020/LDM-2020-10-05.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
# C# Language Design Meeting for October 5th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [`record struct` primary constructor defaults](#record-struct-primary-constructor-defaults)
|
||||
2. [Changing the member type of a primary constructor parameter](#changing-the-member-type-of-a-primary-constructor-parameter)
|
||||
3. [`data` members](#data-members)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "The problem with people who voted for immutable by default is that they can't change their opinion. #bad_dad_jokes"
|
||||
|
||||
## Discussion
|
||||
|
||||
### `record struct` primary constructor defaults
|
||||
|
||||
We picked up today where we left off [last time](LDM-2020-09-30.md#primary-constructors-and-data-properties), looking at what
|
||||
primary constructors should generate in `record struct`s. We have 2 general axes to debate: whether we should generate mutable
|
||||
or immutable members, and whether those members should be properties or fields. All 4 combinations of these options are valid
|
||||
places that we could land, with various pros and cons, so we started by examing the mutable vs immutable axis. In C# 9, `record`
|
||||
primary constructors mean that the properties are generated as immutable, and consistency is a strong argument for preferring
|
||||
immutable in structs. However, we also have another analogous feature in C#: tuples. We decided on mutability there because it's
|
||||
more convenient, and struct mutability does not carry the same level of concern as class mutability does. A struct as a
|
||||
dictionary key does not risk getting lost in the dictionary unless it itself references mutable class state, which is just as
|
||||
much of a concern for class types as it is for struct types. Even if we had `with` expressions at the time of tuples, it's
|
||||
likely that we still would have had the fields be mutable. A number of C# 7 features centered around reducing unnecessary struct
|
||||
copying, such as `readonly` members and ref struct improvements, and reducing copies in large structs by `with` is still a
|
||||
useful goal. Finally, we have a better story for making a `struct` fully-`readonly` with 1 keyword, while we don't have a similar
|
||||
story for making a `struct` fully-mutable with a similar gesture.
|
||||
|
||||
Next, we examined the question of properties vs fields. We again looked to our previous art in tuples. `ValueTuple` can be viewed
|
||||
as an anonymous struct record type: it has value equality and is used as a pure data holder. However, `ValueTuple` is a type
|
||||
defined in the framework, and its implementation details are public concern. As a framework-defined pure data holder, it has no
|
||||
extra behavior to encapsulate. A `record struct`, on the other hand, is not a public framework type. Much like any other user-
|
||||
defined `class` or `struct`, the implementation details are not public concern, but the concern of the creator. We have real
|
||||
examples in the framework (such as around the mathematics types) where exposing fields instead of properties was later regretted
|
||||
because it limits the future flexibility of the type, and we feel the same level of concern applies here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Primary constructors in `record struct`s will generate mutable properties by default. Like with `record` `class`es, users will
|
||||
be able to provide a definition for the property if they do not like the defaults.
|
||||
|
||||
### Changing the member type of a primary constructor parameter
|
||||
|
||||
In C# 9, we allow `record` types to redefine the property generated by a primary constructor parameter, changing the accessibility
|
||||
or the accessors. However, we did not allow them to change whether the member is a field or property. This is an oversight, and
|
||||
we should allow changing whether the member is a field or property in C# 10. This will allow overriding of the default decision
|
||||
in the first section, giving an ability for a "grow-up" story for tuples into named `record struct`s with mutable fields if the
|
||||
user wishes.
|
||||
|
||||
### `data` members
|
||||
|
||||
Finally today, we took another look at `data` members, and what behavior they should have in `record struct`s as opposed to
|
||||
`record` `classes`. We had previously decided that `data` members should generate `public` `init` properties in `record` types;
|
||||
therefore, the crucial thing to decide is if `data` should mean the same thing as `record` would in that type, or if the `data`
|
||||
keyword should be separated from `record` entirely. In C# today, we have very few keywords that change the code they generate
|
||||
based on containing type context, and making `data` be dependent on whether the member is in a `struct` or `class` could end up
|
||||
being quite confusing. On the other hand, if `data` is "the short way to create a nominal record type", then having different
|
||||
behavior between positional parameters and `data` members in a `struct` could also be confusing.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We did not reach a decision on this today. There are 3 proposals on the table:
|
||||
|
||||
1. A `data` member is `public string FirstName { get; set; }` in `struct` types, and `public string FirstName { get; init; }` in
|
||||
`class` types.
|
||||
2. A `data` member is `public string FirstName { get; init; }` in all types.
|
||||
3. We cut `data` entirely.
|
||||
|
||||
We'll come back to this in a future LDM.
|
103
meetings/2020/LDM-2020-10-07.md
Normal file
103
meetings/2020/LDM-2020-10-07.md
Normal file
|
@ -0,0 +1,103 @@
|
|||
# C# Language Design Meeting for October 7th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [`record struct` syntax](#record-struct-syntax)
|
||||
2. [`data` members redux](#data-members-redux)
|
||||
3. [`ReadOnlySpan<char>` patterns](#readonlyspanchar-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "And we're almost there (famous last words, I'll knock on something)"
|
||||
- "What about `record delegate`... does `record interface` make sense... I'll go away now"
|
||||
|
||||
## Discussion
|
||||
|
||||
### `record struct` syntax
|
||||
|
||||
First, we looked at what syntax we would use to specify `struct` types that are records. There are two possibilities:
|
||||
|
||||
```cs
|
||||
record struct Person;
|
||||
struct record Person;
|
||||
```
|
||||
|
||||
In the former, `record` is the modifier on a `struct` type. In the latter, `struct` is the modifier on `record` types.
|
||||
We also considered whether to allow `record class`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
By unanimous agreement, `record struct` is the preferred syntax, and `record class` is allowed. `record class` is
|
||||
synonymous with just `record`.
|
||||
|
||||
### `data` members redux
|
||||
|
||||
With `data`, we come back to the same question we had at the end of [Monday](LDM-2020-10-05.md#data-members): should we have
|
||||
`data` members, and if we do, what should they mean. `data` can be viewed as a strategy to try and get nominal records in a
|
||||
single line, much like positional records. This is not a goal of brevity just for brevity's sake: in discriminated unions,
|
||||
listing many variants as nominal records is a design goal, and single-line declarations are particularly useful for this case.
|
||||
Many languages with discriminated unions based on inheritance introduce short class-declaration syntax for use in union
|
||||
declarations, and that was a defining goal for where `record` types would be useful.
|
||||
Another area worth examining is the original proposal for the `data` keyword. In the original proposal, primary constructors
|
||||
would have meant the same thing in `record` types as well non-`record` types, and `data` would have been used as a modifier
|
||||
on the primary constructor parameter to make it a public get/init property. `data` applied to a member, then, would have been
|
||||
a natural extension and reuse of that keyword. With the original scenario gone, there are a few concrete scenarios we think
|
||||
are highly related to, and will influence, the `data` keyword:
|
||||
|
||||
1. Use as a single-line in a discriminated union. While this is motivating, it's worth considering that, at least to some LDT
|
||||
members, anything more than 2 properties as `data` members doesn't look great, and would perhaps work better as a multi-line
|
||||
construct, which will line up visually. This seems a reasonable concern, so `data` may not be the solution we're looking for.
|
||||
2. Use in required properties, as it seems likely that we will need some keyword to indicate that a property is a required
|
||||
member in a type. While `data` could be orthogonal to some other required property keyword, it's likely there will be at least
|
||||
some interaction as we'd likely want `data` to additionally imply required. It's also possible that we could have just a single
|
||||
keyword to mark a property required, and it gives you what we believe are the useful defaults for such a property.
|
||||
3. Use in general primary constructors as a way to say "give me the same thing a `record` would have for this parameter". This
|
||||
would be somewhat resurrecting the original use case for the keyword, as we could then retcon `record` primary constructors
|
||||
as implying `data` on all parameters for you, but you can then opt-in to them in regular primary constructors as well. `data`
|
||||
members in a class, then, would again become a natural reuse and extension of the keyword on a primary constructor parameter.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll leave `data` out of the language for now. Our remaining motivating scenarios are things that will be worked on more in
|
||||
C# 10 cycle, and absent clearer designs in those spaces the need and design factors for `data` are too abstract. Once we work
|
||||
more on these 3 scenarios, we'll revisit `data` and see if a need for it has emerged.
|
||||
|
||||
### `ReadOnlySpan<char>` patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1881
|
||||
|
||||
We have a community PR to implement the Any Time feature of allowing constant strings to be used to pattern match against
|
||||
a `ReadOnlySpan<char>`. This would be acknowledging special behavior for `ReadOnlySpan<char>` with respect to constant strings,
|
||||
but we already have acknowledged special behavior for `Span` and `ReadOnlySpan` in the language, around `foreach`. We also
|
||||
considered whether this could be a breaking change, but we determined it could not be one: `ReadOnlySpan` cannot be converted
|
||||
to `object` or to an interface as it is a ref struct, so if there exists a pattern today operating on one today the input type
|
||||
of that pattern match must be `ReadOnlySpan<char>`. There were two open questions:
|
||||
|
||||
#### Should we allow `Span<char>`
|
||||
|
||||
The question is if `Span<char>` should also be allowed as well as `ReadOnlySpan<char>`. All of the same arguments about
|
||||
compat apply to `Span<char>`. We also considered whether `Memory`/`ReadOnlyMemory` should be allowed inputs. Unlike `Span`/`ReadOnlySpan`,
|
||||
though, there are backcompat concerns with `Memory` that cannot be overlooked, as they are not ref structs. It is very
|
||||
easy to obtain a `Span`/`ReadOnlySpan` from a `Memory`/`ReadOnlyMemory`, however, so the need isn't as great.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
`Span<char>` is allowed. `Memory<char>`/`ReadOnlyMemory<char>` are not.
|
||||
|
||||
#### Is this specific to `switch`, or can it be any pattern context
|
||||
|
||||
The original proposal here just mentioned `switch`. However, the implementation allows it to be used in any pattern context,
|
||||
such as `is`.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Allowed in all pattern contexts.
|
||||
|
||||
|
||||
#### Final Notes
|
||||
|
||||
We also discussed making sure that switching on a `Span`/`ReadOnlySpan` is as efficient for large switch statements as switching
|
||||
on a string is. Over 6 cases, the compiler will take the hashcode of the string and use it to implement a jump table to reduce the
|
||||
number of string comparisons necessary. This method is added to the `PrivateImplementationDetails` class in an assembly, and we
|
||||
should make sure to do the same thing for `Span<char>` and `ReadOnlySpan<char>` here as well so the cost to using one isn't higher
|
||||
than allocations would be from just doing a substring and matching with that.
|
57
meetings/2020/LDM-2020-10-12.md
Normal file
57
meetings/2020/LDM-2020-10-12.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# C# Language Design Meeting for October 12th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. General improvements to the `struct` experience (continued)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Only 5 people are going to use it..."
|
||||
- "We said that about function pointers too."
|
||||
|
||||
## C# Community Ambassadors
|
||||
|
||||
We have been taking a look at https://github.com/dotnet/csharplang/discussions/3878, which is around how to help community
|
||||
proposals into better shape to be looked at by LDM. We largely agree with the points raised, which is that most community
|
||||
issues are in a state that isn't quite good enough to be sponsored by an LDT member, but also not controversial or generally
|
||||
discouraged enough to be outright rejected. Our move to enabling discussions on the repo itself was the first step in this
|
||||
direction: discussions are more free-form, allow multiple branching conversations, and we view them as having less requirements
|
||||
towards creating one. The next step we're taking today is nominating a few community members to become ambassadors to the
|
||||
community: @jnm2, @YairHalberstadt, and @svick. This role will be focussed around helping triage incoming issues and
|
||||
discussions, helping community members get their proposals into a state that can realistically be looked at by LDT members
|
||||
and potentially championed, and helping with deduplication as it is noticed. We're starting very small with this experiment:
|
||||
if it proves successful, we can consider expanding the list to more members of the csharplang community, of which there are
|
||||
several deserving candidates. As part of this, we're tentatively hoping to review promising community proposals at a more
|
||||
regular cadence, hopefully monthly.
|
||||
|
||||
Community ambassadors are not members of the LDT and do not have the ability to champion issues. They will help us look at
|
||||
deserving community proposals, and we value their input, as we value the input of the general community here.
|
||||
|
||||
## Discussion
|
||||
|
||||
Today, we finished going through the fixed fields proposal, found [here](https://github.com/dotnet/csharplang/blob/master/proposals/low-level-struct-improvements.md).
|
||||
[Previously](LDM-2020-09-23.md), we made it through the `Provide ref fields` section of the specification. Today, we
|
||||
finished going through the rest. Again, most the conversation was dedicated to the specification itself, but there were
|
||||
a few points brought up that will be updated in the specification later:
|
||||
|
||||
* `ThisRefEscapes` is defined very narrowly in this proposal, not allowed on any virtual methods (including methods from
|
||||
interfaces). In our initial investigations, we don't see a huge need for allowing it on interface methods. We can consider
|
||||
this in the future if it ends up being a friction point, but will need a good amount of work around ensuring that OHI is
|
||||
correctly respected.
|
||||
* We considered the issue of whether we should use syntax for `ThisRefEscapes` and `DoesNotEscape`, and nearly-unanimously
|
||||
decided on using attributes. Attributes allow us to have a more descriptive name that users are less likely to accidentally
|
||||
use. Further, all this attribute is controlling is a `modreq`, not the actual implementation of the method. We have existing
|
||||
attributes such as `SpecialNameAttribute` that control emit flags like this, so it's not unprecedented.
|
||||
* The syntax for fixed buffer locals is actually quite generally attractive: it would be nice if we could remove the requirement
|
||||
for specifying `fixed` in fields. It would further simplify the language: we even have parser code that parses this form today
|
||||
so that we can give nicer errors to people coming from C/C++. It would further resolve an ambiguity: in the proposal today, old-
|
||||
style fixed-size buffers are differentiated from new-style by whether or not the field is in an unsafe context: by omitting the
|
||||
fixed, we have a completely different syntax that is unambiguous.
|
||||
* We don't believe there is any real motivating scenario for either fixed multi-dimensional arrays or fixed jagged arrays of a
|
||||
specific inner length. Jagged arrays of this form would work: `int[] array[10]`, where you have a fixed buffer of array references,
|
||||
but allocating the inner array as part of the containing structure itself isn't currently seen as an important scenario.
|
||||
Multidimensional arrays today need to call into CLR helper methods today and are generally slower. We can think about this later
|
||||
if a scenario comes up.
|
||||
* We might want to make "inline array" a first-class type in the CLR. This would allow for things such as substituting in type
|
||||
parameters. This will largely be driven by the CLR design here.
|
||||
|
138
meetings/2020/LDM-2020-10-14.md
Normal file
138
meetings/2020/LDM-2020-10-14.md
Normal file
|
@ -0,0 +1,138 @@
|
|||
# C# Language Design Meeting for October 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Triage](#triage)
|
||||
1. [Repeated Attributes in Partial Members](#repeated-attributes-in-partial-members)
|
||||
2. [Permit a fixed field to be declared inside a readonly struct](#permit-a-fixed-field-to-be-declared-inside-a-readonly-struct)
|
||||
3. [Do not require fixing a fixed field of a ref struct](#do-not-require-fixing-a-fixed-field-of-a-ref-struct)
|
||||
4. [params Span<T>](#params-spant)
|
||||
5. [Sequence Expressions](#sequence-expressions)
|
||||
6. [utf8 string literals](#utf8-string-literals)
|
||||
7. [pattern-based `with` expressions](#pattern-based-with-expressions)
|
||||
8. [Property improvements](#property-improvements)
|
||||
9. [File scoped namespaces](#file-scoped-namespaces)
|
||||
10. [Discriminated Unions](#discriminated-unions)
|
||||
11. [Efficient params and string formatting](#efficient-params-and-string-formatting)
|
||||
12. [Allow omitting unused parameters](#allow-omitting-unused-parameters)
|
||||
2. [Milestone Simplification](#milestone-simplification)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I used to have a fidget cube, but then [redacted] took away my fidget cube because it was apparently a very annoying device for everyone else"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Triage
|
||||
|
||||
#### Repeated Attributes in Partial Members
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3658
|
||||
|
||||
We discussed this briefly at the tail-end of C# 9 work, and came to the conclusion this is an issue for source generator authors and that we wanted
|
||||
to make it work. Triaged into the working set.
|
||||
|
||||
#### Permit a fixed field to be declared inside a readonly struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1793
|
||||
|
||||
This is part of the feature we discussed Monday. Triage into the working set.
|
||||
|
||||
#### Do not require fixing a fixed field of a ref struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1792
|
||||
|
||||
We generally like the idea of this feature: language-wise it's small, makes sense, and is an annoyance for users of ref structs. However, the
|
||||
implementation of `fixed` in Roslyn has historically been an issue with a long bug trail coming every time we need to make a change. We think
|
||||
this makes sense the next time we need to a larger feature around fixed that would force us to refactor the handling of `fixed` in the compiler.
|
||||
Until then, we don't think the implementation cost is worth it. Triaged to the backlog.
|
||||
|
||||
#### params Span<T>
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1757
|
||||
|
||||
We like this feature. We'll need to carefully design the overload resolution rules such that it wins out over existing params arrays. Libraries
|
||||
can then intentionally opt-in by introducing `Span<T>` overloads, just like they can introduce any new overload today that causes a change in
|
||||
behavior when recompiled. Triaged into the working set.
|
||||
|
||||
#### Sequence Expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/377
|
||||
|
||||
Related to #3038, #3037, and #3086, which are all in the working set. We'll be taking a look at the whole scenario in the upcoming design period,
|
||||
so this is triaged into the working set with the others.
|
||||
|
||||
#### utf8 string literals
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/184
|
||||
|
||||
We need the runtime to make progress here. While we could consider ways to make it easier to declare constant utf-8 byte arrays, we feel that
|
||||
would likely box us in when the runtime wants to move forward in this area. When they're ready, we can put this back on the agenda. Triaged
|
||||
into the backlog.
|
||||
|
||||
#### pattern-based `with` expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/162
|
||||
|
||||
This is part of the next round of record work, so into the working set it goes. We may need to think about how general object might be able to
|
||||
do object reuse as part of a `with` (Roslyn would not be able use it to replace `BoundNode.Update` as spec'd for records, for example), so that
|
||||
is a scenario we need to keep in mind as we generalize.
|
||||
|
||||
#### Property improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/133
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
We've discussed these recently in LDM. We're moving forward with design, starting with #133. Triaged into the working set.
|
||||
|
||||
#### File scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
Triaged into the working set. We'll need to come up with a more complete proposal than we have currently, particularly considering how it will
|
||||
interact with top-level statements, but it doesn't seem too difficult.
|
||||
|
||||
#### Discriminated Unions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/113
|
||||
|
||||
This is one of the next big C# tent poles we're looking to address, and we're working on an updated proposal after having some time to ruminate
|
||||
on the previous proposals in the area and post initial records. We have lots of design work to do, so into the working set it goes.
|
||||
|
||||
#### Efficient params and string formatting
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2302
|
||||
|
||||
Interpolated strings are a pit of failure in certain scenarios, such as logging, where formatting costs are incurred up front even if they're
|
||||
not needed. We'll keep this in the working set to keep iterating on proposals. We know we want to do some work here, but we're not sure exactly
|
||||
how it will function yet.
|
||||
|
||||
#### Allow omitting unused parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2180
|
||||
|
||||
We had some initial questions around the metadata representation of discards: should the be nameless, for example? We generally like proposals
|
||||
that put discards in more places, and we're willing to look at a complete proposal if one is presented. Triaged to any time.
|
||||
|
||||
### Milestone Simplification
|
||||
|
||||
Today, we have quite a few milestones that mean various things at various points in time, and it's hard for outsiders to keep track of what is
|
||||
on track for what. This is further complicated by the fact that sometimes things are just not known: we could be working on a feature with every
|
||||
intention to put it in the next version of C#, but fully acknowledging it might not make it. Or we may be doing design work for a feature that
|
||||
we know _definitely_ will not make it into the next version of C#, but needs to have work done or we'll never get it at all. For this reason,
|
||||
we're simplifying our milestones to be more clear about what the state of things is:
|
||||
|
||||
* Working Set is the set of proposals that the LDT is currently actively working on. Not everything in this milestone will make the next version
|
||||
of C#, but it will get design time during the upcoming release.
|
||||
* Backlog is the set of proposals that members of the LDT have championed, but are not actively working on. While discussion and ideas from the
|
||||
community are welcomed on these proposals, the cost of the design work and implementation review on these features is too high for us to consider
|
||||
community implementation until we are ready for it.
|
||||
* Any Time is the set of proposals that members of the LDT have championed, but are not being actively worked on and are open to community
|
||||
implementation. We'll go through these shortly and label the ones that need to have a specification added vs the ones that have an approved spec
|
||||
and just need implementation work. Those that need a specification still need to be presented during LDM for approval of the spec, but we are
|
||||
willing to take the time to do so at our earliest convenience.
|
||||
* Likely Never is the set of proposals that the LDM has reject from the language. Without strong need or community feedback, these proposals will
|
||||
not be considered in the future.
|
||||
* Numbered milestones are the set of features that have been implemented for that particular language version. For closed milestones, these are
|
||||
the set of things that shipped with that release. For open milestones, features can be potentially pulled later if we discover compatability or
|
||||
other issues as we near release.
|
132
meetings/2020/LDM-2020-10-21.md
Normal file
132
meetings/2020/LDM-2020-10-21.md
Normal file
|
@ -0,0 +1,132 @@
|
|||
# C# Language Design Meeting for October 21st, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Primary Constructors](#primary-constructors)
|
||||
2. [Direct Parameter Constructors](#direct-parameter-constructors)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Hopefully Seattle doesn't wash into the ocean"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Primary Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4025
|
||||
|
||||
We started today by examining the latest proposal around primary constructors, and attempting to tease out the possible
|
||||
behaviors of what a primary constructor could mean. Given this sample code:
|
||||
|
||||
```cs
|
||||
public class C(int i, string s) : B(s)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
there are a few possible behaviors for what those parameters mean:
|
||||
|
||||
1. Parameter references are only allowed in initialization contexts. This means you could assign them to a property, but
|
||||
you couldn't use them in a method that runs after the class has been initialized.
|
||||
2. Parameter references are allowed throughout the class, and if they're referenced from a non-initialization context then
|
||||
they are captured in the type, but are not considered fields from a language perspective. This is the proposed behavior in
|
||||
the linked discussion.
|
||||
3. Parameter references are automatically captured to fields of the same name.
|
||||
|
||||
We additionally had a proposal in conjunction with behavior 1: You can opt in to having a member generated by adding an
|
||||
accessibility to the parameter. So `public class C(private int i)` would generate a private field `i`, in addition to having
|
||||
a constructor parameter. This is conceivably not tied to behavior 1 however, as it could also apply to behavior 2 as well.
|
||||
It would additionally need some design work around what type of member is generated: would `public` generate a field or a
|
||||
property? Would it be mutable or immutable by default?
|
||||
|
||||
To try and come up with a perferred behavior here, we started by taking a step back and examining the motivation behind
|
||||
primary constructors. Our primary (pun kinda intended) motivation is that declaring a property and initializing it from
|
||||
a constructor is a boring, repetitive, boilerplate-filled process. You have to repeat the type twice, and repeat the name
|
||||
of the member 4 times. Various IDE tools can help with generating these constructors and assignments, but it's still a lot
|
||||
of boilerplate code to read, which obscures the actually-interesting bits of the code (such as validation). However, it is
|
||||
_not_ a goal of primary constructors to get to 1-line classes: we feel that this need is served by `record` types, and that
|
||||
actual classes are going to have some behavior. Rather, we are simply trying to reduce the overhead of describing the simple
|
||||
stuff to let the real behavior show through more strongly.
|
||||
|
||||
With that in mind, we examined some of the merits and disadvantages of each of these:
|
||||
1. We like that parameters look like parameters, and adding an accessibility makes it no longer look like a parameter. There's
|
||||
definitely a lot to debate on what that accessibility should actually do though. There are some concerns that having the
|
||||
parameter not be visible is non-obvious to users: to solve this, we could make then visible throughout the type, but have it
|
||||
be an error to reference in a location that is not an initialization context (and a codefix to add an accessibility to make
|
||||
it very easy to fix). This allows users to be very explicit about the lifetime of variables.
|
||||
2. This variation of the proposal might feel more natural to users, as the variable exists in an outer "scope" and is therefore
|
||||
visible to all inner scopes. There is some concern, however, that silent captures could mean that the state of a class is no
|
||||
longer visible: you'll have to examine all methods to determine if a constructor parameter is captured, which could be suboptimal.
|
||||
3. This the least flexible of the proposals, and wasn't heavily discussed. It would need some amount of work to fit in with the
|
||||
common autoprop case, where the others could work without much work (either via generation or by simple assignment in an
|
||||
initializer for the autoprop).
|
||||
|
||||
In discussing this, we brought another potential design: we're considering primary constructors to eliminate constructor boilerplate.
|
||||
What if we flipped the default, and instead generated a constructor based on the members, rather than generating potential members
|
||||
based on a constructor. A potential strawman syntax would be something like this:
|
||||
```cs
|
||||
// generate constructor and assignments for A and B, because they are marked default
|
||||
public class C
|
||||
{
|
||||
default public int A { get; }
|
||||
default public string B { get; }
|
||||
}
|
||||
```
|
||||
There are a bunch of immediate questions around this: how does ordering work? What if the user has a partial class? Does this
|
||||
actually solve the common scenario? While we think the answer to this is no, it does bring up another proposal that we
|
||||
considered in the C# 9 timeframe while considering records: Direct Parameter Constructors.
|
||||
|
||||
## Direct Parameter Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4024
|
||||
|
||||
This proposal would allow constructors to reference members defined in a class, and the constructor would then generate a matching
|
||||
parameter and initialization for that member in the body of the constructor. This has some benefits, particularly for class types:
|
||||
|
||||
* Many class types have more than one constructor. It's not briefer than primary constructors declaring members for a class with
|
||||
just one constructor, but it does get briefer as you start adding more constructors.
|
||||
* We believe, at least from our initial reactions, that this form would be easier to understand than accessibility modifiers on the
|
||||
parameters.
|
||||
|
||||
There are still some open questions though. You'd like to be able to use this feature in old code, but if we don't allow for customizing
|
||||
the name of the parameter, then old code won't be able to adopt this for properties, as properties will almost certainly have different
|
||||
casing than the parameters in languages with casing. This isn't something we can just special case for the first letter either: there
|
||||
are many examples (even in Roslyn) of identifiers that have the first two letters capitalized in a property and have them both lowercase
|
||||
in a parameter (such as `csharp` vs `CSharp`). We briefly entertained the idea of making parameter names match in a case-insensitive
|
||||
manner, but quickly backed away from this as case matters in C#, working with casing in a culture-sensitive way is a particularly hard
|
||||
challenge, and wouldn't solve all cases (for example, if a parameter name is shortened compared to the property).
|
||||
|
||||
We also examined how this feature might interact with the accessibility-on-parameter proposal in the previous section. While they are
|
||||
not mutually exclusive, several members of the LDT were concerned that having both of these would add too much confusion, giving too
|
||||
many ways to accomplish the same goal. A read of the room found that we were unanimously in favor of this proposal over the accessibility
|
||||
proposal, and there were no proponents of adding both proposals to the language.
|
||||
|
||||
Finally, we started looking at how initialization would work with constructor chaining. Some example code:
|
||||
|
||||
```cs
|
||||
public class Base {
|
||||
public object Prop1 { get; set; }
|
||||
public virtual object Prop2 { get; set; }
|
||||
public Base(Prop1, Prop2) { Prop2 = 1; }
|
||||
}
|
||||
|
||||
public class Derived : Base
|
||||
{
|
||||
public new string Prop1 { get; set; }
|
||||
public override object Prop2 { get; set; }
|
||||
public Derived(Prop1, Prop2) : base(Prop1, Prop2) { }
|
||||
}
|
||||
```
|
||||
|
||||
Given this, the question is whether the body of `Derived` should initialize `Prop1` or `Prop2`, or just one of them, or neither of them.
|
||||
The simple proposal would be that passing the parameter to the base type always means the initialization is skipped, but that would
|
||||
mean that the `Derived` constructor has no way to initialize the `Prop1` property, as it can no longer refer to the constructor parameter
|
||||
in the body, and `Base` certainly couldn't have initialized it (since it is not visible there). There are a few questions like this that
|
||||
we'll need to work out.
|
||||
|
||||
## Conclusions
|
||||
|
||||
Our conclusions today are that we should pursue #4024 in ernest, and come back to primary constructors with that in mind. Several members
|
||||
are not convinced that we need primary constructors in any form, given that our goal is not around making regular `class` types have only
|
||||
one line. Once we've ironed out the questions around member references as parameters, we can come back to primary constructors in general.
|
109
meetings/2020/LDM-2020-10-26.md
Normal file
109
meetings/2020/LDM-2020-10-26.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
# C# Language Design Meeting for October 26st, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Pointer types in records](#pointer-types-in-records)
|
||||
2. [Triage](#triage)
|
||||
1. [readonly classes and records](#readonly-classes-and-records)
|
||||
2. [Target typed anonymous type initializers](#target-typed-anonymous-type-initializers)
|
||||
3. [Static local functions in base calls](#static-local-functions-in-base-calls)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "And I specialize in taking facetious questions and answering them literally"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Pointer types in records
|
||||
|
||||
Today, you cannot use pointer types in records, because our lowering will use `EqualityComparer<T>.Default`, and pointer
|
||||
types are not allowed as generic type arguments in general. We could specially recognize pointer types here, and use a
|
||||
different equality when comparing fields of that type. We have a similar issue with anonymous types, where pointers are
|
||||
not permitted for the same reason (and indeed, Roslyn's code for generating the equality implementation is shared between
|
||||
these constructs). We would also need consider every place record types can be used if we enabled this: for example, what
|
||||
would the experience be when attempting to pattern deconstruct on a record type, as pointer types are not allowed in patterns
|
||||
today? It also might not be a good idea to introduce value equality based on pointer types to class types, as this is not
|
||||
well-defined for all pointer types (function pointers, for example). Finally, the runtime has talked several times about
|
||||
enabling pointer types as generic type parameters, and if they were to do so then the rules for this might fall out at that
|
||||
time.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged to the Backlog. We're not convinced this needs to be something that we enable right now, and may end up being resolved
|
||||
by fallout from other changes.
|
||||
|
||||
### Triage
|
||||
|
||||
#### readonly classes and records
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3885
|
||||
|
||||
This proposal would allow marking a class type `readonly`, ensuring that all fields and properties on the type must be `readonly`
|
||||
as well. Several familiar questions were immediately raised, namely around the benefit. `readonly` has a very specific benefit
|
||||
for struct types, around allowing the compiler to avoid defensive copies where they would otherwise be necessary. For
|
||||
`readonly` classes, there is no clear similar advantage. We might not even emit such information to metadata, and the main
|
||||
benefit would be for type authors, not for type consumers. There is also some concern about whether this would be confusing to
|
||||
users, particularly if this does not apply to an entire hierarchy. If you depend on a non-Object base type that has mutable,
|
||||
then the benefits of using `readonly` are not as clear, even for a type author. Similarly, if a non-`readonly` type can inherit
|
||||
from a `readonly` type, that means that any guarantees on the current type aren't very strong, as mutation can occur under the
|
||||
hood anyway. `readonly` in C# today always means shallow immutability, so there is an argument to be made that this level of
|
||||
hierarchy-mutability is not too different.
|
||||
We also looked at the question of whether this feature should just be analyzer. There is certainly argument for that: particularly
|
||||
if there is no hierarchy impact, it seems a perfect use case for an analyzer. However, this is a case where we allow the keyword
|
||||
on one set of types, while not allowing it on a different set of types. Further, unlike many such proposals, we already have a
|
||||
C# keyword that is perfect for the scenario.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set. We'll look at this with low priority, and particularly try to see what the scenarios around
|
||||
hierarchical enforcement look like, as those were more generally palatable to LDT members.
|
||||
|
||||
#### Target typed anonymous type initializers
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3957
|
||||
|
||||
This is a proposal to address some cognitive dissonance we have with object creation in C#: you can leave off the parens if you
|
||||
have an object initializer, but only if you specify the type. While it does save 2 characters, that is not a primary motivation
|
||||
of this proposal. There are grow-up stories for other areas we could explore in this space as well: we could allow F#-style
|
||||
object expressions, for example, or borrow from Java and allow anonymous types to actually inherit from existing types/interfaces.
|
||||
However, we have a number of concerns about the compat aspects of doing this, where adding a new `object` overload can silently
|
||||
change consumer code to call a different overload and create an anonymous type. In these types of scenarios, it might even be
|
||||
impossible to determine if the user made an error: if they typed a wrong letter in the property name, for example, we might be
|
||||
forced to create an anonymous type silently, instead of erroring on the invalid object initializer.
|
||||
|
||||
We also briefly considered more radical changes to the syntax: for example, could we allow TS/JS-style object creation, with
|
||||
just the brackets? However, this idea was not very well received by the LDM.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Backlog. While we're open to new proposals in this space that significantly shift the bar (such as around new
|
||||
ways of creating anonymous types that inherit from existing types), we think that this proposal could end up conflicting with
|
||||
any such future proposals and should be considered then.
|
||||
|
||||
#### Static local functions in base calls
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3980
|
||||
|
||||
This is a proposal that, depending on the exact specification, would either be a breaking change or have complicated lookup rules
|
||||
designed to avoid the breaking change. It also requires some deep thought into how the exact scoping rules would work. Today,
|
||||
locals introduced in the `base` call are visible throughout the constructor, so we would have to retcon the scoping rules to
|
||||
work something like this:
|
||||
|
||||
1. Outermost scope, contains static local functions
|
||||
2. Middle scope, contains the base clause and any variables declared there
|
||||
3. Inner scope, contains the method body locals and regular local functions.
|
||||
|
||||
This also raises the question of whether we should stop here. For example, it might be nice if `const` locals could be used as
|
||||
parameter default values, or if attributes could use names from inside a method body. We've had a few proposals for creating
|
||||
various parts of a "method header" scope (such as https://github.com/dotnet/csharplang/issues/373), we could consider extending
|
||||
that generally to allow this type of thing. Another question would be: why stop at `static` local functions? We could allow
|
||||
regular local functions in the base clause, and leverage definite assignment to continue doing the same things it does today to
|
||||
make sure that things aren't used before assignment. This might work well with a general "method header" scope, instead of the
|
||||
scheme proposed above. Finally, we considered simply allowing the `base` call to be done in the body of the constructor instead,
|
||||
a la Visual Basic. This has some support, and would allow us to avoid the question of a method header scope by simply allowing
|
||||
users to move the base call to where the local function is visible.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set. We like the idea, and have a few avenues to explore around method header scopes or allowing the
|
||||
base call to be moved.
|
60
meetings/2020/LDM-2020-11-04.md
Normal file
60
meetings/2020/LDM-2020-11-04.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# C# Language Design Meeting for November 4th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Nullable parameter defaults](#nullable-parameter-defaults)
|
||||
2. [Argument state after call for AllowNull parameters](#argument-state-after-call-for-allownull-parameters)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
No particularly amusing quotes were said during this meeting, sorry.
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nullable parameter defaults
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/4101
|
||||
|
||||
We started today by examining some declaration cases around nullable that we special cased in our our initial implementation, but
|
||||
felt that we should re-examine in light of `T?`. In particular, today we do not warn when you create a method signature that assigns
|
||||
`default` to `T` as a default value. This means that it's possible for generic substitution to cause bad method signatures to be
|
||||
created, where a `null` is assigned to a non-nullable reference type. The proposal, then, is to start warning about these cases, in
|
||||
both C# 8 and 9. In C# 8, the workaround is to use `AllowNull` on that parameter, and C# 9 would allow `T?` for that parameter. There
|
||||
was no pushback to this proposal.
|
||||
|
||||
As part of this, we also considered the other locations in this example. For example, we could issue a warning at the callsite of such
|
||||
a method. The proposal would be to expand the warnings on nullable mismatches in parameters to implicit parameters as well. This could
|
||||
end up causing double warning, if both the method and the callsite get a warning here, but it might be able to help users who are using
|
||||
otherwise unannotated methods, or libraries compiled with an older version of the compiler that did not warn here. There is some
|
||||
concern, though, that putting a warning at the callsite is the wrong location. It was the method author that created this invalid
|
||||
signature, and we'd be punishing users with additional warnings. Presumably, if the author allows `null`, they're appropriately
|
||||
handling it, even if the code is still oblivious or in an older version of C#.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The original proposal is approved. We'll continue looking at the callsite proposal as an orthogonal feature and come back when we have
|
||||
a complete proposal to review.
|
||||
|
||||
### Argument state after call for AllowNull parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4102
|
||||
https://github.com/dotnet/roslyn/issues/48605
|
||||
|
||||
Next, we looked at fallout over a previous decision to update the state of variables passed as parameters to a method. This allowed us
|
||||
to bring the behavior of `void M([NotNull] string s)` and `void M(string s)` in line, which caused issues for the BCL (as it meant
|
||||
that any change to add `NotNull` to a parameter would be a good change to make, and they were not interested in updating thousands of
|
||||
methods to do this). However, it caused an unfortunate side effect: `void M([AllowNull] string s)` would have no warnings, and would
|
||||
silently update the parameter state to not null, even though there was absolutely no way `M` could have affected the input argument as
|
||||
it was not passed by ref. We considered 2 arguments for this:
|
||||
|
||||
1. Perhaps the method isn't annotated correctly? The real-world example here is `JsonConvert.WriteJson`, and there is an argument to be
|
||||
made that in C# 9, this parameter would just be declared as `T?`, solving the issue. However, it does feel somewhat obvious that this
|
||||
method shouldn't update the state of the parameter.
|
||||
2. We loosen the "effective" resulting type of the parameter based on the precondition, if the parameter is by-value. `[AllowNull]` would
|
||||
loosen the effective resulting type to `T?`, which would not make any changes to the current state of the argument. We might also do the
|
||||
inverse for `DisallowNull`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We ran out of time today, but are interested in approach 2 above. We'll come back with a complete proposal for a future LDM and examine it
|
||||
again.
|
127
meetings/2020/LDM-2020-11-11.md
Normal file
127
meetings/2020/LDM-2020-11-11.md
Normal file
|
@ -0,0 +1,127 @@
|
|||
# C# Language Design Meeting for November 11th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [IsRecord in metadata](#isrecord-in-metadata)
|
||||
2. [Triage](#triage)
|
||||
1. [AsyncMethodBuilder](#asyncmethodbuilder)
|
||||
2. [Variable declarations under disjunctive patterns](#variable-declarations-under-disjunctive-patterns)
|
||||
3. [Direct constructor parameters](#direct-constructor-parameters)
|
||||
4. [Always available extension methods](#always-available-extension-methods)
|
||||
5. [Allow `nameof` to access instance members from static contexts](#allow-nameof-to-access-instance-members-from-static-contexts)
|
||||
6. [Add `await` as a dotted postfix operator](#add-await-as-a-dotted-postfix-operator)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Alright, I'm going to make an analogy to social security here."
|
||||
|
||||
## Discussion
|
||||
|
||||
### IsRecord in metadata
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4121
|
||||
|
||||
We discussed a few different ways to tackle this issue, which relates to customers depending on the presence of the `<Clone>$` method
|
||||
as a way of determining if a type is a `record` or not. First, there are theoretically some ways we could retrofit this method to work
|
||||
as an identifying characteristic, such as by marking `<Clone>$` methods on non-record types, instead of marking the record types in
|
||||
some manner. However, this approach would have to square with `struct` records, which may or may not have that special method. We also need
|
||||
to understand some of the dependent scenarios better: we understand the IDE scenario pretty well, we want to be able to have QuickInfo
|
||||
and metadata-as-source reflect the way the type was declared. However, we don't have an understanding of the EF scenario, and what it
|
||||
would want to do for, say, a non-record class that inherits from a record type. Finally, we considered time frames, and came to the
|
||||
conclusion that the proposed solution would work fine if we wait until C# next to introduce it, and does not require being rushed out the
|
||||
door to be retconned into C# 9: the proposed solution is backwards compatible, as long as it is introduced at the same time as
|
||||
class/record cross inheritance.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Into the Working Set, we'll consider this issue in conjunction with class/record cross-inheritance.
|
||||
|
||||
### Triage
|
||||
|
||||
#### AsyncMethodBuilder
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1407
|
||||
|
||||
We generally like this proposal, as it solves a real need in the framework while creating a generalized feature that can be plugged into
|
||||
more libraries. We did have a couple of questions come up:
|
||||
1. Should we allow this on just the method, or also the type/module level? This seems to be similar to `SkipLocalsInit`, and could be
|
||||
tedious to rep-specify everywhere.
|
||||
2. Can this solve `ConfigureAwait`? We don't think so: this controls the method builder, not the meaning of `await`s inside the method,
|
||||
so while it could potentially change whether a method call returns a task that synchronizes to the thread context by default, it could
|
||||
only do that for methods defined in your assembly, which would just lead to confusing behavior.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set, we'll work through the proposal in a full LDM session soon.
|
||||
|
||||
#### Variable declarations under disjunctive patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4018
|
||||
|
||||
We like this proposal. There are a couple of open issues/questions that need to be addressed:
|
||||
1. We need a rule that says when you are allowed to redeclare existing variables. It needs to cover multiple switch case labels, while
|
||||
also not permitting things declared outside the switch label to be redeclared.
|
||||
2. How identical do the types need to be? Are nullability differences permitted? ie, are `(object?, object)` and `(object, object?)` the
|
||||
same for the purposes of this feature? It seems like they may have to be.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set. We'll take some time and consider these questions, and we should also consider alternatives at the same time,
|
||||
such as an `into` pattern that would allow a previously-declared variable to be assigned in a pattern, including ones declared outside a
|
||||
pattern.
|
||||
|
||||
#### Direct constructor parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4024
|
||||
|
||||
We discussed this feature during our last look at primary constructors, and our conclusion is that we need to explore the space more fully
|
||||
with both features in mind. There are concerns about abstraction leaks, particularly with property casing.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set, to be considered in conjunction with primary constructors.
|
||||
|
||||
#### Always available extension methods
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4029
|
||||
|
||||
There was some strong negative reaction to this proposal. However, presented another way it's more interesting: users who use `var` need
|
||||
to include `using`s they otherwise do not need in order to access these types of extension methods, whereas users who do not use `var`
|
||||
will already have the relevant `using` in scope, and will thus see these extension methods. These types of methods are also often ways of
|
||||
working around various C# limitations, such as lack of specialization, and would naturally be defined on the type itself if it was possible.
|
||||
|
||||
We are concerned with doing anything in this space with extension everything/roles/type classes on the horizon, as we don't want to change
|
||||
extension methods in a way that we'd regret with those features.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the backlog. We'll consider this in conjunction with extension everything.
|
||||
|
||||
#### Allow `nameof` to access instance members from static contexts
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4037
|
||||
|
||||
There is some feeling that this is basically just a bug in the spec (or is just an area where it's not super clear, and it's a bug in the
|
||||
implementation). We do think this is generally good: yes, the scenario could just use `string.Length`, but that is not really what the user
|
||||
intended. They wanted the `Length` property on `P`, and if `P` changes to a different type that no longer has `Length`, there should be an
|
||||
error there. Without this, the cliff that `nameof` tries to solve is just moved further, not removed.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into Any Time. We'd accept a community contribution here: it needs to only permit exactly this scenario, not allow any new types of
|
||||
expressions in `nameof`.
|
||||
|
||||
#### Add `await` as a dotted postfix operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4076
|
||||
|
||||
The LDT has very mixed reactions on this. While we are sympathetic to the desire to make awaits more chainable, and the `.` can be viewed
|
||||
as the pipeline operator of the OO world, we don't think this solves enough to make it worth it. Chainability of `await` expressions
|
||||
isn't the largest issue on our minds with `async` code today: that honor goes to `ConfigureAwait`, which this does not solve. We could go
|
||||
a step further with this form by making it a general function that would allow `true`/`false` parameters to control the thread context
|
||||
behavior, but given our mixed reaction to the syntax form as a whole we're not optimistic about the approach. A more general approach that
|
||||
simplified chaining generally for prefix operators would be more interesting.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. We do like the space of improving `await`, but we don't think this is the way.
|
81
meetings/2020/LDM-2020-11-16.md
Normal file
81
meetings/2020/LDM-2020-11-16.md
Normal file
|
@ -0,0 +1,81 @@
|
|||
# C# Language Design Meeting for November 16th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Ternary comparison operator](#ternary-comparison-operator)
|
||||
2. [Nominal and collection deconstruction](#nominal-and-collection-deconstruction)
|
||||
3. [IgnoreAccessChecksToAttribute](#ignoreaccesscheckstoattribute)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "If it turns out to be really hard, give it to a smarter compiler dev"
|
||||
- "The name is not good: It should be the BreakTermsOfServiceAttribute"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Ternary comparison operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4108
|
||||
|
||||
This proposal centers around add a bit of syntax sugar to simply binary comparisons, where a user might want to compare 3 objects
|
||||
for ascending or descending order. Today, the user would have to write `a < b && b < c`, but with this proposal they would just
|
||||
write `a < b < c`. In order to deal with the potential ambiguities, we'd have to first attempt to bind these scenarios as we would
|
||||
today, and if that fails then attempt to bind them as this new "relational chaining" form. This feature would need to have a very
|
||||
specific pattern: if we were to allow `a < b > c`, for example, that could be syntactically ambiguous with a generic, and would need
|
||||
to keep binding to that as it would today. We therefore are only interested in strictly-ordered comparisons: all comparisons in a
|
||||
chain should be less-than/less-than-or-equal, or greater-than/greater-than-or-equal, without mixing between the 2 orders. We are
|
||||
also worried about the compile-time cost of double-binding here, particularly since the most-likely binding will have to be done second,
|
||||
in order to preserve backwards compatability. We also considered allowing more than 3 objects in such a chain: we like the idea, but
|
||||
it will require some spec work as it does not just fall out of the current specification.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into Any Time. This needs some specification work to allow the 4 or more operators, which would likely be similar in form to
|
||||
the null-conditional operator. Additionally, any implementation will have to take steps to address potential perf issues and demonstrate
|
||||
that it does not adversely affect compilation perf on real codebases.
|
||||
|
||||
### Nominal and collection deconstruction
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4082
|
||||
|
||||
This feature provides unity between patterns and deconstruction assignment. Today, we have tuple deconstruction assignment, and tuple
|
||||
patterns. They evolved in the opposite direction: we started with tuple deconstruction assignment, then added general patterns to the
|
||||
language. We now consider adding nominal deconstruction assignment, to complete the symmetry between the feature sets.
|
||||
|
||||
One thing we want to be careful of here is to not go to far down the path of replicating patterns in assignment. A pattern in an `is`
|
||||
or `switch` forces the user to deal with the case that the pattern did not match, which is not present here. For nominal deconstruction,
|
||||
we can leverage nullable reference types: the user will get a warning if they attempt to deconstruct an element that could be null. For
|
||||
list patterns, though, there is no similar level of warning, and we want to be careful of creating a pit of failure that will result in
|
||||
exceptions at runtime. We are also concerned about some of the aspects of allowing names to be given to outer structures, such as allowing
|
||||
`var { Range: { Column: column } range } = GetRange();`. This could mix badly with allowing existing variable reuse: in patterns today,
|
||||
the `{ ... } identifier` syntax always introduces a new variable, which we think would end up being confusing. We very wary of allowing
|
||||
patterns to match into existing variables because it would introduce side-effects to patterns, which is very concerning. Finally, given
|
||||
that we haven't yet designed regular list patterns, we think we should hold off on list deconstruction assignment until those are complete,
|
||||
at which point we should have a discussion around whether we should have them at all.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Nominal deconstruction assignment is accepted into the working set. Let's split list deconstruction assignment into a separate issue, which
|
||||
will be followed up on after list patterns are designed. Open questions exist on whether we should allow names on patterns themselves.
|
||||
|
||||
### IgnoreAccessChecksToAttribute
|
||||
|
||||
We had a very spirited discussion around this attribute, which is essentially the inverse of `InternalsVisibleToAttribute`. Where IVT
|
||||
allows an author to grant access to a specific other dll, this allows a specific other dll to grant themselves access to an author. There
|
||||
are many challenges around this scheme that fundamentally affect the entire ecosystem, and those discussions need to happen at a .NET
|
||||
ecosystem level, rather than at a language level, even though most of the implementation work will fall on the compiler. Ref assemblies,
|
||||
for example, do not have internal members today. There also needs to be discussions on how we would enforce the "use at your own risk"
|
||||
aspect of this feature. We can say that all we want, but at the end of the day if VS were to take a dependency on an internal Roslyn
|
||||
API that we need to change, it could block shipping until either Roslyn readded the API or the dependency was removed. Given our
|
||||
experiences with `InternalsVisibleToAttribute` already, we're not certain that this burden of risk will be correctly shouldered by the
|
||||
ones actually taking on the risk.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Tabled for now. Discussion needs to happen at a higher level.
|
||||
|
||||
## Working Set Themes
|
||||
|
||||
With our discussions today, we have finished working through our current triage backlog! We've collected the various issues and themes
|
||||
in our working set and created a meta-issue to track them all: https://github.com/dotnet/csharplang/issues/4144. We've locked the issue
|
||||
to ensure that it stays a clean space. For discussion on a particular topic, please see the topic issue, or create a new discussion.
|
75
meetings/2020/LDM-2020-12-02.md
Normal file
75
meetings/2020/LDM-2020-12-02.md
Normal file
|
@ -0,0 +1,75 @@
|
|||
# C# Language Design Meeting for December 2nd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Partner scenarios in roles and extensions](#partner-scenarios-in-roles-and-extensions)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "Have you noticed how similar what you just said is to function pointers?"
|
||||
- "This is a modern version of COM aggregation." "But in a good way."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Partner scenarios in roles and extensions
|
||||
|
||||
Today, we heard from partner teams on the Azure SDK and on ASP.NET, talking about friction they currently encounter
|
||||
with some scenarios that might be addressable through roles, extension everything, or potentially some other solution.
|
||||
|
||||
#### ASP.NET Feature Collections
|
||||
|
||||
ASP.NET models contexts through an aggregation system that allows different services to be composed onto a single `HttpContext`.
|
||||
For example, adding TLS to a session involves creating a TLS connection, wrapping the existing connection, and adding it
|
||||
as an available feature to the context. This underlying connection could be one of several types connections: it could be
|
||||
routed through a socket, or it could be in-process, or any of a number of other connection mechanisms, each with its own set
|
||||
of properties. It is possible to retrieve each of these sets of features from a `Get` method on the context, but this is
|
||||
cumbersome and not generally extensible: for their users, it would be nice to be able to expose a view over a context or
|
||||
connection that exposed the underlying properties.
|
||||
|
||||
This scenario seems like a clear-cut use case for roles and extension-everything as we last discussed them. A role could be
|
||||
used to expose a grouping of properties on an upper layer from a lower layer. In fact, the ASP.NET architecture was designed
|
||||
with the eventual intention of using extension properties to remove a number of extension methods that they have in place
|
||||
today to expose these underlying properties from a decorated type. Of the 3 scenarios we discussed today, this seems the
|
||||
most obviously-addressed by the existing proposal.
|
||||
|
||||
#### Azure SDKs
|
||||
|
||||
The Azure SDK scenario presents a more interesting challenge. Feature collections were designed with C# in mind, meaning
|
||||
that both types and type names were thoughtfully designed when creating the API. The Azure SDK (and by extension many
|
||||
web APIs), by comparison, are designed in a web-first manner. In this context, property _names_ are important, and general
|
||||
structures of an API are important, but names of these structures are _not_ important. These APIs are often described and
|
||||
generated using Swagger, which uses JSON to describe the structure of a response. JSON structures can be strongly typed,
|
||||
of course: the structure itself is the type. But the nested properties of a JSON object, which can be nested objects
|
||||
themselves, are described entirely in a structural manner, not in a nominal manner as we do in C#. Here, C#'s paradigms
|
||||
break down, and the SDK teams run into trouble when creating a C# API to wrap this structure. All of these nested structures
|
||||
need to be named, so that C# can talk about them. This leads to an explosion of types, which can be made even more difficult
|
||||
when you consider identical structures developed by different teams (perhaps even different companies). By necessity, each
|
||||
team will need to create their own "named" data structure to give C# an ability to talk about the object, but these names
|
||||
are really meaningless. The JSON didn't have this name, and the structures cannot unify. There are also scenarios with
|
||||
very similar objects (perhaps one object has an extra field that the former does not have). This necessitates an entirely
|
||||
new object to be created, and users often end up needing to write boilerplate methods that just translate objects from
|
||||
representation A to B, changing nothing about the data other than making sure the type's name lines up.
|
||||
|
||||
This set of scenarios is not addressed by roles and extension methods, as they currently stand. We theorized that teams
|
||||
might be able to a combination of `dynamic` and a custom completion provider/analyzer, to give users help in writing and
|
||||
validating code that is, in essence, a set of dynamic calls to nested properties of unnamed types that does have a structure,
|
||||
but this is a complicated solution to the scenario that is likely not generally-leverageable. There are more IDEs than just
|
||||
VS, and more IDE scenarios than just completion: what would go-to-def do on these, or quick info on hover?
|
||||
|
||||
#### Data Processing
|
||||
|
||||
Finally, we took a look at a small proof-of-concept library exploring what replicating parts of the popular Python library
|
||||
Pandas could be like in C#, with stronger typing around the data generated from a given input. This scenario is very
|
||||
reminiscent of F# type providers, allowing users to simply dot into a data structure. However, it suffers from the same
|
||||
set of issues that affect the Azure SDK scenarios above. In order to talk about nested data structures, they have to be
|
||||
named. And while the Azure examples entirely focus on the types of structures representable in JSON, Pandas is far more
|
||||
flexible. Additionally, Pandas allows you create new objects as they flow through a pipeline, adding or removing properties
|
||||
as they are manipulated.
|
||||
|
||||
Looking at these last two examples, it seems that there are some scenarios not served well by C# today, involving JSON or
|
||||
other structured but unnamed data. These scenarios care deeply about the names of properties, and the structure attached to
|
||||
each property name, but the domains these scenarios interact with do not care about naming these structures. Further, adding
|
||||
names to these structures in C# can be harmful, because it locks out scenarios that can be accomplished in the original
|
||||
domain and forces a naming structure where none was intended, which can result in confusing or badly-named types that can
|
||||
then never be changed because of backwards compatibility concerns. As we continue to evolve the roles and extension
|
||||
everything proposals, we should look at these scenarios and see if there are ways to improve the experience for them.
|
30
meetings/2020/LDM-2020-12-07.md
Normal file
30
meetings/2020/LDM-2020-12-07.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# C# Language Design Meeting for December 7th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required Properties](#required-properties)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "I also can't see anyone's video, so raise your hand [in Teams] if you're not here."
|
||||
- "If you can't solve required properties, you're not making a time machine."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4209
|
||||
|
||||
Today, we took a look at the next revision of the required properties proposal, after a few months of design work from a smaller
|
||||
team to flesh out the design. We had a small questions coming out of the meeting:
|
||||
|
||||
* Could assignments in the nominal parameter list always imply `base.`? It would make it easier for automatically considering
|
||||
hidden properties being initialized.
|
||||
* We could make it more user friendly by possibly adding warning when a property that is required by the constructor is definitely
|
||||
assigned in the constructor?
|
||||
* There's still some debate as to this should only be a source-breaking change.
|
||||
* Is `init` the right word? Maybe `requires` would be better?
|
||||
|
||||
More generally, the reaction to this in the LDM was mixed. While we believe that this is the best proposal we've seen to date, it's
|
||||
very complicated and introduces a bunch of new concepts. We may need to start looking at simplifying scenarios and seeing whether that
|
||||
allows us to cut this proposal down a bit.
|
94
meetings/2020/LDM-2020-12-14.md
Normal file
94
meetings/2020/LDM-2020-12-14.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# C# Language Design Meeting for December 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [List Patterns](#list-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "My Monday is your Friday... you're going to get unadulterated truth here"
|
||||
|
||||
## Discussion
|
||||
|
||||
### List Patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3245
|
||||
|
||||
Today, we took our first in-depth look at list patterns, which will be the next big turn of the crank in generalized pattern
|
||||
support in C#. The intent of this type of pattern is to allow matching on arbitrary collection types, matching against both
|
||||
the length and the content of the collection in a single, simple-to-understand fashion, much like our other pattern forms enable
|
||||
for positional and nominal content. To bring context to the discussion around our general goals and principles for pattern
|
||||
matching in C#, we again brought up the table from discussion [3107](https://github.com/dotnet/csharplang/discussions/3107):
|
||||
|
||||
| Type | Declaration | Creation | Decomposition | Dispatch (pattern) |
|
||||
|-|:-:|:-:|:-:|:-:|
|
||||
|`int`|`int x`|`3`|`int x`|`int x` or `3`
|
||||
|class with mutable fields|`class Point { public int X, Y; }`|`new Point { X = 3, Y = 4 }`|`p.X`|`Point { X: int x, Y: int y }` or `Point { X: 3, Y: 4 }`
|
||||
|anonymous type|-|`new { X = 3, Y = 4 }`|`p.X`|`{ X: int x, Y: int y }` or `{ X: 3, Y: 4 }`
|
||||
|record class|`class Point(int X, int Y);` (proposed)|`Point(3, 4)` (proposed)|`(int x, int y) = p`|`Point(int x, int y)` or `Point(3, 4)`
|
||||
|tuple|`(int x, int y)`|`(3, 4)`|`(int x, int y) = p`|`(int x, int y)` or `(3, 4)`
|
||||
|List|`List<int>`|`new List<int> { 3, 4 }`| ? | **List patterns fit in here**
|
||||
|Dictionary|`Dictionary<K,V>`|`new Dictionary<K,V> { { K, V } }`| ? | ?
|
||||
|
||||
While we are not looking at list decomposition with this proposal, we should keep it in mind, as we will want whatever form
|
||||
we use for pattern dispatch to be used for decomposition as well. In this table, we can see a correspondence between the different
|
||||
syntactic forms of creation and destructuring: object initializers correspond with recursive object patterns, tuple literals
|
||||
correspond with tuple patterns, etc. By this principle, our initial instinct is to make the collection pattern correspond with
|
||||
the collection initializer syntax, which uses curly braces. However, this runs into an immediate issue: `{ }` is already a
|
||||
valid pattern today, the empty object pattern. This means it cannot serve as the empty collection pattern, as that pattern
|
||||
must specifically check that the collection is actually empty. The current proposal instead takes the approach of using square
|
||||
brackets `[]` to represent a collection pattern, instead of using the curlies. We're pretty divided on this approach: C# has
|
||||
not used square brackets to represent a list or array in the past. Even C# 1.0 used the curly brackets for array initializers,
|
||||
reserving the brackets for array size or index access. This would make the proposed syntax a really big break with C# tradition.
|
||||
We could "retcon" this by enabling new types of collection literals using square brackets, but that's an issue that LDM has
|
||||
not intensely looked at beyond previously rejecting https://github.com/dotnet/csharplang/issues/414 and related issues. After
|
||||
some discussion, we've come to the realization that the empty collection (ie, the base case for recursive algorithms) is the
|
||||
most important pattern to design for, and the rest of the syntax falls out from that design. We've come up with a few different
|
||||
syntactic proposals:
|
||||
|
||||
1. The existing proposal as is. Notably, this pattern form is _not_ part of a recursive pattern, and that means that you can't
|
||||
specify a pattern like this: `int[] [1, 2, 3]`. Indeed, such a pattern is potentially ambiguous, as `int[] []` already means
|
||||
a type test against a jagged `int` array today. Instead, such a pattern would have to be expressed as `int[] and []`. The
|
||||
first part narrows the type to `int[]`, and the second part specifies that the array must be empty. We're not huge fans of needing
|
||||
the `and` combinator for a base case (when the input type to the pattern is not narrowed enough to use a collection pattern)
|
||||
given that one is not needed for tuple deconstruction patterns or property patterns, but it is elegant in its simplicity.
|
||||
2. A similar version to 1, except that it allows nesting the square brackets inside the curly braces of the property pattern.
|
||||
This would allow `int[] { [1, 2, 3] }` for the case where you need to both narrow the input type and test the array content.
|
||||
There are some concerns with this syntax: we've also envisioned a dictionary pattern, that would match content using a form
|
||||
like this: `{ [1]: { /* some nested pattern */ } }`. This would mean that the colon at the end of the brackets would determine
|
||||
whether the contents of the brackets are used as arguments to an indexer or the patterns the collection is being tested against.
|
||||
3. Using curlies to match the list contents. There are a couple of sub proposals in this section, separated by the way they
|
||||
enable testing for the empty collection case. They share the content tests, which look like `{ 1, 2, 3 }`.
|
||||
1. No empty case. Instead, use a property pattern on `Length` or `Count` to check for the empty case. This has issues
|
||||
with our previously-desired support for `IEnumerable` and general `foreach`-able type support, as they do not have any
|
||||
such property to check.
|
||||
2. Empty case represented by a single comma: `{,}` would represent an array with `Length == 0`. This was suggested, but
|
||||
no one argued in favor.
|
||||
3. Square brackets for `Length` tests. This proposal would look something like this: `int[] [0]`. The interesting angle
|
||||
with this version is that it allows for succinct length tests that could be composed of patterns itself. For example, the
|
||||
BCL has some cases where they need to check to see whether an array has some content and Length between two cases, and that
|
||||
could be expressed as `[>= 0 and < 256] { 1, 2, .. }`. This would also allow a general length check to be expressed for
|
||||
`foreach`-able types, though there are some concerns that it would become a perf pitfall if enumerating the entire sequence
|
||||
was necessary to check a non-zero length. The length or count of the collection could also be extracted with a declaration
|
||||
pattern, which could turn into a nice shorthand for not having to know whether this collection uses `Length` or `Count`,
|
||||
something we didn't standardize and now can't. How this version combines with other property tests on the same object would
|
||||
still need to be discussed: could you do `MyType { Property: a } [10] { 1, 2, 3, .. }`, for example, or would the property and
|
||||
collection patterns need to be combined with an `and`?
|
||||
4. Add a new combinator keyword to make the transition to a collection pattern explicit. This is similar to how VB uses
|
||||
`From` to indicate collection initializers. Such a pattern might look something like `int[] with { }` for the empty case.
|
||||
(`with` was the word we spitballed here, but likely wouldn't end up being the final word for confusion with `with` expressions).
|
||||
|
||||
We came to no solid conclusions on the syntax topic today, as we were mostly generating ideas and need some time to mull over the
|
||||
various forms. We'll come back to this at a later date.
|
||||
|
||||
We also took a brief look at the slice pattern and whether it could be extended to `foreach`-able types. A trailing `..` in a
|
||||
`foreach` match would be easy to implement and not have any hidden costs, as it would just skip a check to `MoveNext()` after
|
||||
the leading bits of the pattern match. However, a leading `..` would be much more concerning. Depending on implementation
|
||||
strategy, we'd have emit a much larger state machine or keep track of a potentially large number of previous values as we
|
||||
iterate the enumerable, so that when we get to the end we can ensure that the previous slots matched correctly. We're not
|
||||
sure if this difference will be obvious enough to users, and will need to think more about whether we should enable the trailing
|
||||
slice, or enable both slice patterns and let the codegen be what it will. In all likelihood, if the user needs this pattern
|
||||
they're going to code it by hand if they can't do it with a pattern, and we can make it less likely to introduce a bug for it
|
||||
if we generate the states programmatically instead of the user doing it by hand.
|
||||
|
||||
Again, we came to no solid conclusions here, as we spent most of our time on the syntax aspects.
|
87
meetings/2020/LDM-2020-12-16.md
Normal file
87
meetings/2020/LDM-2020-12-16.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# C# Language Design Meeting for December 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [List patterns](#list-patterns)
|
||||
2. [Definite assignment changes](#definite-assignment-changes)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "They are syntax forms you little devil. No amount pedanticism is too much in C#."
|
||||
|
||||
## Discussion
|
||||
|
||||
### List patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3245
|
||||
|
||||
Coming out of [Monday's meeting](LDM-2020-12-14.md), we had a few different competing proposals for syntax. As a quick recap:
|
||||
|
||||
1. The original proposal as is.
|
||||
2. Put the brackets from 1 inside the braces on the top level.
|
||||
3. Use braces for the list pattern, with the empty case being:
|
||||
1. No empty case.
|
||||
2. `{,}`
|
||||
3. Add a `[pattern]` form that allows testing and potentially extracting the length of a collection.
|
||||
4. Add a new combinator to make the braces explicitly a list pattern, which would allow `{ }` to be the base case.
|
||||
|
||||
After the notes were published, we took the list and had an email discussion to narrow in on the specifics of each of these cases.
|
||||
Cases 2, 3.i, 3.ii, and 3.iv were not defended in this email chain, and coming into today's meeting there were 4 different main syntax
|
||||
proposals, the final 3 being variations of 3.iii from the original list (in psuedo-antlr):
|
||||
|
||||
1. The original proposal. This introduces a new `pattern`, which uses `'[' (pattern (',' pattern)* ']')` as the syntax of that
|
||||
new pattern. This cannot be expressed as a top-level concept in a `positional_pattern` or a `property_pattern` because the braces
|
||||
can be ambiguous with the `type` component of these patterns.
|
||||
2. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns_or_list_element_patterns '}')?`.
|
||||
This form modifies the `positional_pattern` syntax introduced in C# 8 to add a length pattern section, defined by the middle
|
||||
`[pattern]` section, and modifies the final braces to contain either a set of positional subpatterns, or a set of list element
|
||||
patterns, but not both. To test both property elements and list elements, a conjunctive pattern needs to be used.
|
||||
3. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns '}')? ('{' list_subpatterns '}')?`.
|
||||
This is very similar to 2, except that it allows both property and list subpatterns at the same time.
|
||||
4. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns_and_list_element_patterns '}')?`.
|
||||
This is very similar to 2, except that it allows both property subpatterns and list subpatterns in the same set of braces. Consider
|
||||
subproposals of this version to require properties first, list elements first, or no ordering requirements.
|
||||
|
||||
The very important goal for the language team here is to follow the correspondence principle. That means that if you construct using one
|
||||
syntax construct, you should use the same construct to deconstruct. For collection types, this means that we strongly want to prefer
|
||||
using curly braces as the deconstruction syntax, rather than square brackets, because collection initializers use the braces. It is
|
||||
possible that at some point in the future, we could add a collection literal syntax that uses square brackets, but there is strong
|
||||
history in C# to avoid using the brackets in this fashion. Up to this point, the brackets have always contained indexes or lengths
|
||||
in the language, and lists of things to initialize have always been inside braces. Changing that at this point, even if we later seek
|
||||
to add conformance by introducing a new collection literal, would be asking C# users to unlearn a concept that has been unchanged
|
||||
since C# 1.0, which is very concerning to us. Given this desire, option 1 deviates too much from existing C#, and we will instead
|
||||
focus on one of the latter options.
|
||||
|
||||
Of these latter options, option 2 can be viewed as a strict subset of both 3 and 4, as either will allow using conjunctive patterns
|
||||
to separate out the list and property patterns if users feel that the combination is unreadable. Additionally, we again turn to the
|
||||
correspondence principle: today, you cannot combine both collection initializers and object initializers. By the correspondence
|
||||
principle, then, you should not be able to combine them in the same pattern during deconstruction. We're not necessarily opposed to
|
||||
allowing collection and object initializers to be combined in the future, but that is out of scope for the collection pattern changes.
|
||||
|
||||
Finally, in discussions Monday and over email, we also took a brief look at indexer patterns as possible `property_subpatterns`. These
|
||||
would look something like this: `{ [1]: pattern, [2..^4]: var slice }`. This form seems like a good next step after list patterns to
|
||||
allow deconstructioning objects with indexers. These indexer arguments could allow non-integer constants as well as multiple arguments,
|
||||
giving a deconstruction mechanism for dictionaries that corresponds to object initializers in this area.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll move forward with the general syntax proposed in option 2, with a length subpattern and allowing either property subpatterns or
|
||||
list subpatterns in the same "recursive pattern" section. We still need to translate the psuedo-spec above into a formal specification.
|
||||
We also did not address the open questions around whether the length pattern should be applicable to types of `IEnumerable`, and if so
|
||||
whether `[0]` is the only allowed pattern or if any pattern is allowable.
|
||||
|
||||
### Definite assignment changes
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4240
|
||||
|
||||
This is an area of longstanding pain for C# users: any time conditional evaluation and comparison to constants mix, definite assignment
|
||||
cannot figure out what is going on and variables that the user can see are obviously assigned are not considered assigned. We're highly
|
||||
in support of this idea in general, as everyone has run into this at some point or another in their C# careers. The definite assignment
|
||||
rules are written in a very syntax-driven form, and thus this proposal is written in a very syntax-driven form to update the relevant
|
||||
constructs. Despite that, we do wonder whether we can make these rules more holistic and systematic, such that we don't need to make
|
||||
them syntax-specific like they need to be today. We're also less enthused about the conditional expression version. If it fell out of
|
||||
more general rules it would be nice, but it's not highly important like the null conditional and coalescing changes seem to be.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The general idea is approved. We'll work to see if we can generalize the rules a bit, and submit a proposal for review on github.
|
|
@ -1,35 +1,452 @@
|
|||
# Upcoming meetings for 2020
|
||||
# C# Language Design Notes for 2020
|
||||
|
||||
## Schedule ASAP
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## Schedule when convenient
|
||||
## Dec 16, 2020
|
||||
|
||||
## Recurring topics
|
||||
[C# Language Design Notes for December 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-16.md)
|
||||
|
||||
- *Triage championed features*
|
||||
- *Triage milestones*
|
||||
- *Design review*
|
||||
- List patterns
|
||||
- Definite assignment changes
|
||||
|
||||
## Dec 14, 2020
|
||||
|
||||
[C# Language Design Notes for December 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-14.md)
|
||||
|
||||
- List patterns
|
||||
|
||||
## Dec 7, 2020
|
||||
|
||||
[C# Language Design Notes for December 7th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-07.md)
|
||||
|
||||
- Required Properties
|
||||
|
||||
## Dec 2, 2020
|
||||
|
||||
[C# Language Design Notes for December 2nd, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-02.md)
|
||||
|
||||
- Partner scenarios in roles and extensions
|
||||
|
||||
## Nov 16, 2020
|
||||
|
||||
[C# Language Design Notes for November 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-16.md)
|
||||
|
||||
- Triage
|
||||
|
||||
## Nov 11, 2020
|
||||
|
||||
[C# Language Design Notes for November 11th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-11.md)
|
||||
|
||||
- IsRecord in metadata
|
||||
- Triage
|
||||
|
||||
## Nov 4, 2020
|
||||
|
||||
[C# Language Design Notes for November 4th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-04.md)
|
||||
|
||||
- Nullable parameter defaults
|
||||
- Argument state after call for AllowNull parameters
|
||||
|
||||
## Oct 26, 2020
|
||||
|
||||
[C# Language Design Notes for October 26st, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-26.md)
|
||||
|
||||
- Pointer types in records
|
||||
- Triage
|
||||
|
||||
## Oct 21, 2020
|
||||
|
||||
[C# Language Design Notes for October 21st, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-21.md)
|
||||
|
||||
- Primary Constructors
|
||||
- Direct Parameter Constructors
|
||||
|
||||
## Oct 14, 2020
|
||||
|
||||
[C# Language Design Notes for October 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-14.md)
|
||||
|
||||
- Triage
|
||||
- Milestone Simplification
|
||||
|
||||
## Oct 12, 2020
|
||||
|
||||
[C# Language Design Notes for October 12th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-12.md)
|
||||
|
||||
- General improvements to the `struct` experience (continued)
|
||||
|
||||
## Oct 7, 2020
|
||||
|
||||
[C# Language Design Notes for October 7th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-07.md)
|
||||
|
||||
- `record struct` syntax
|
||||
- `data` members redux
|
||||
- `ReadOnlySpan<char>` patterns
|
||||
|
||||
## Oct 5, 2020
|
||||
|
||||
[C# Language Design Notes for October 5th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md)
|
||||
|
||||
- `record struct` primary constructor defaults
|
||||
- Changing the member type of a primary constructor parameter
|
||||
- `data` members
|
||||
|
||||
## Sep 30, 2020
|
||||
|
||||
[C# Language Design Notes for September 30th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-30.md)
|
||||
|
||||
- `record structs`
|
||||
- `struct` equality
|
||||
- `with` expressions
|
||||
- Primary constructors and `data` properties
|
||||
|
||||
## Sep 28, 2020
|
||||
|
||||
[C# Language Design Notes for September 28th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-28.md)
|
||||
|
||||
- Warning on `double.NaN`
|
||||
- Triage
|
||||
|
||||
## Sep 23, 2020
|
||||
|
||||
[C# Language Design Notes for September 23rd, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-23.md)
|
||||
|
||||
- General improvements to the `struct` experience
|
||||
|
||||
## Sep 16, 2020
|
||||
|
||||
[C# Language Design Notes for September 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-16.md)
|
||||
|
||||
- Required Properties
|
||||
- Triage
|
||||
|
||||
## Sep 14, 2020
|
||||
|
||||
[C# Language Design Notes for September 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-14.md)
|
||||
|
||||
- Partial method signature matching
|
||||
- Null-conditional handling of the nullable suppression operator
|
||||
- Annotating IEnumerable.Cast
|
||||
- Nullability warnings in user-written record code
|
||||
- Tuple deconstruction mixed assignment and declaration
|
||||
|
||||
## Sep 9, 2020
|
||||
|
||||
[C# Language Design Notes for September 9th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-09.md)
|
||||
|
||||
- Triage issues still in C# 9.0 candidate
|
||||
- Triage issues in C# 10.0 candidate
|
||||
|
||||
## Aug 24, 2020
|
||||
|
||||
[C# Language Design Notes for August 24th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-08-24.md)
|
||||
|
||||
- Warnings on types named `record`
|
||||
- `base` calls on parameterless `record`s
|
||||
- Omitting unnecessary synthesized `record` members
|
||||
- [`record` `ToString` behavior review](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md#printing-members-printmembers-and-tostring-methods)
|
||||
- Behavior of trailing commas
|
||||
- Handling stack overflows
|
||||
- Should we omit the implementation of `ToString` on `abstract` records
|
||||
- Should we call `ToString` prior to `StringBuilder.Append` on value types
|
||||
- Should we try and avoid the double-space in an empty record
|
||||
- Should we try and make the typename header print more economic
|
||||
- Reference equality short circuiting
|
||||
|
||||
## Jul 27, 2020
|
||||
|
||||
[C# Language Design Notes for July 27th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-27.md)
|
||||
|
||||
- [Improved nullable analysis in constructors](https://github.com/RikkiGibson/csharplang/blob/nullable-ctor/proposals/nullable-constructor-analysis.md) (Rikki)
|
||||
- [Equality operators (`==` and `!=`) in records](https://github.com/dotnet/csharplang/issues/3707#issuecomment-661800278) (Fred)
|
||||
- `.ToString()` or `GetDebuggerDisplay()` on records? (Julien)
|
||||
- Restore W-warning to `T t = default;` for generic `T`, now you can write `T?`? (Julien)
|
||||
|
||||
## Jul 20, 2020
|
||||
|
||||
[C# Language Design Notes for July 20th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-20.md)
|
||||
|
||||
- [struct private fields in definite assignment](https://github.com/dotnet/csharplang/issues/3431) (Neal/Julien)
|
||||
- [Proposal 1](https://github.com/dotnet/roslyn/issues/30194#issuecomment-657858716)
|
||||
- [Proposal 2](https://github.com/dotnet/roslyn/issues/30194#issuecomment-657900257)
|
||||
- Finish [Triage](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+no%3Amilestone)
|
||||
- Records-related features to pick up in the next version of C# (Mads)
|
||||
|
||||
|
||||
## Jul 13, 2020
|
||||
|
||||
[C# Language Design Notes for July 13th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-13.md)
|
||||
|
||||
- Triage open issues
|
||||
|
||||
## Jul 6, 2020
|
||||
|
||||
[C# Language Design Notes for July 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-06.md)
|
||||
|
||||
- [Repeat Attributes in Partial Members](https://github.com/RikkiGibson/csharplang/blob/repeated-attributes/proposals/repeat-attributes.md) (Rikki)
|
||||
- `sealed` on `data` members
|
||||
- [Required properties](https://github.com/dotnet/csharplang/issues/3630) (Fred)
|
||||
|
||||
|
||||
## Jul 1, 2020
|
||||
|
||||
[C# Language Design Notes for July 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-01.md)
|
||||
|
||||
- [Non-defaultable struct types](https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573) (Sam, Chuck)
|
||||
- Confirm unspeakable `Clone` method and long-term implications (Jared/Julien)
|
||||
|
||||
## Jun 29, 2020
|
||||
|
||||
[C# Language Design Notes for June 29, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md)
|
||||
|
||||
- [Static interface members](https://github.com/Partydonk/partydonk/issues/1) (Miguel, Aaron, Mads, Carol)
|
||||
|
||||
## Jun 24, 2020
|
||||
|
||||
[C# Language Design Notes for June 24, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-24.md)
|
||||
|
||||
- Parameter null checking: finalize syntax
|
||||
- https://github.com/dotnet/csharplang/issues/3275 Variance on static interface members (Aleksey)
|
||||
- [Function pointer question](https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516) (Fred)
|
||||
|
||||
|
||||
## Jun 22, 2020
|
||||
|
||||
[C# Language Design Notes for June 22, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-22.md)
|
||||
|
||||
1. Data properties
|
||||
|
||||
1. Clarifying what's supported in records for C# 9
|
||||
|
||||
- Structs
|
||||
|
||||
- Inheritance with records and classes
|
||||
|
||||
## Jun 17, 2020
|
||||
|
||||
[C# Language Design Notes for June 17, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-17.md)
|
||||
|
||||
1. Null-suppression & null-conditional operator
|
||||
1. `parameter!` syntax
|
||||
1. `T??`
|
||||
|
||||
## Jun 15, 2020
|
||||
|
||||
[C# Language Design Notes for June 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-15.md)
|
||||
|
||||
Record:
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Jun 10, 2020
|
||||
|
||||
[C# Language Design Notes for June 10, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-10.md)
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1711 Roles and extensions
|
||||
|
||||
## Jun 1, 2020
|
||||
|
||||
[C# Language Design Notes for June 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-01.md)
|
||||
|
||||
Records:
|
||||
1. Base call syntax
|
||||
2. Synthesizing positional record members and assignments
|
||||
3. Record equality through inheritance
|
||||
|
||||
## May 27, 2020
|
||||
|
||||
[C# Language Design Notes for May 27, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-27.md)
|
||||
|
||||
Record syntax
|
||||
1. Record structs?
|
||||
2. Record syntax/keyword
|
||||
3. Details on property shorthand syntax
|
||||
|
||||
## May 11, 2020
|
||||
|
||||
[C# Language Design Notes for May 11, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-11.md)
|
||||
|
||||
Records
|
||||
|
||||
## May 6, 2020
|
||||
|
||||
[C# Language Design Notes for May 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-06.md)
|
||||
|
||||
1. Target-typing ?: when the natural type isn't convertible to the target type.
|
||||
1. Allow `if (x is not string y)` pattern.
|
||||
1. Open issues in extension `GetEnumerator`
|
||||
1. Args in top-level programs
|
||||
|
||||
## May 4, 2020
|
||||
|
||||
[C# Language Design Notes for May 4, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-04.md)
|
||||
|
||||
1. Reviewing design review feedback
|
||||
|
||||
## April 27, 2020
|
||||
|
||||
[C# Language Design Notes for April 27, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-27.md)
|
||||
|
||||
Records: positional & primary constructors
|
||||
|
||||
## April 20, 2020
|
||||
|
||||
[C# Language Design Notes for April 20, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-20.md)
|
||||
|
||||
Records: Factories
|
||||
|
||||
## April 15, 2020
|
||||
|
||||
[C# Language Design Notes for April 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-15.md)
|
||||
|
||||
1. Non-void and non-private partial methods
|
||||
2. Top-level programs
|
||||
|
||||
## April 13. 2020
|
||||
|
||||
[C# Language Design Notes for April 13, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-13.md)
|
||||
|
||||
1. Roadmap for records
|
||||
2. Init-only properties
|
||||
|
||||
## April 8, 2020
|
||||
|
||||
[C# Language Design Notes for April 8, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-08.md)
|
||||
|
||||
1. `e is dynamic` pure null check
|
||||
2. Target typing `?:`
|
||||
3. Inferred type of an `or` pattern
|
||||
4. Module initializers
|
||||
|
||||
## April 6, 2020
|
||||
|
||||
[C# Language Design Notes for April 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-06.md)
|
||||
|
||||
1. Record Monday: Init-only members
|
||||
|
||||
## April 1, 2020
|
||||
|
||||
[C# Language Design Notes for April 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-01.md)
|
||||
|
||||
1. Function pointer design adjustments
|
||||
|
||||
2. `field` keyword in properties
|
||||
|
||||
## March 30, 2020
|
||||
|
||||
1. Record Monday
|
||||
|
||||
[C# Language Design Notes for March 30, 2020](LDM-2020-03-30.md)
|
||||
|
||||
## March 25, 2020
|
||||
|
||||
[C# Language Design Notes for March 25, 2020](LDM-2020-03-25.md)
|
||||
|
||||
1. Open issues with native int
|
||||
|
||||
2. Open issues with target-typed new
|
||||
|
||||
## March 23, 2020
|
||||
|
||||
[C# Language Design Notes for March 23, 2020](LDM-2020-03-23.md)
|
||||
|
||||
1. Triage
|
||||
2. Builder-based records
|
||||
|
||||
## March 9, 2020
|
||||
|
||||
[C# Language Design Notes for March 9, 2020](LDM-2020-03-09.md)
|
||||
|
||||
1. Simple programs
|
||||
|
||||
2. Records
|
||||
|
||||
## Feb 26, 2020
|
||||
|
||||
[C# Language Design Notes for Feb. 26, 2020](LDM-2020-02-26.md)
|
||||
|
||||
Design Review
|
||||
|
||||
## Feb 24
|
||||
|
||||
[C# Language Design Notes for Feb. 24, 2020](LDM-2020-02-24.md)
|
||||
|
||||
Taking another look at "nominal" records
|
||||
|
||||
## Feb 19
|
||||
|
||||
[C# Language Design Notes for Feb. 19, 2020](LDM-2020-02-19.md)
|
||||
|
||||
State-based value equality
|
||||
|
||||
## Feb 12
|
||||
|
||||
[C# Language Design Notes for Feb. 12, 2020](LDM-2020-02-12.md)
|
||||
|
||||
Records
|
||||
|
||||
## Feb 10
|
||||
|
||||
[C# Language Design Notes for Feb. 10, 2020](LDM-2020-02-10.md)
|
||||
|
||||
Records
|
||||
|
||||
## Feb 5
|
||||
|
||||
[C# Language Design Notes for Feb. 5, 2020](LDM-2020-02-05.md)
|
||||
|
||||
- Nullability of dependent calls (Chuck, Julien)
|
||||
- https://github.com/dotnet/csharplang/issues/3137 Records as individual features (Mads)
|
||||
|
||||
## Feb 3
|
||||
|
||||
[C# Language Design Notes for Feb. 3, 2020](LDM-2020-02-03.md)
|
||||
|
||||
Value Equality
|
||||
|
||||
## Jan 29, 2020
|
||||
|
||||
[C# Language Design Notes for Jan. 29, 2020](LDM-2020-01-29.md)
|
||||
|
||||
Records: "With-ers"
|
||||
|
||||
## Jan 22, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 22, 2020](LDM-2020-01-22.md)
|
||||
|
||||
1. Top-level statements and functions
|
||||
2. Expression Blocks
|
||||
|
||||
## Jan 15, 2020
|
||||
|
||||
## Jan 13, 2020
|
||||
[C# Language Design Notes for Jan 15, 2020](LDM-2020-01-15.md)
|
||||
|
||||
Records
|
||||
|
||||
1. "programming with data"
|
||||
1. Decomposing subfeatures of records
|
||||
|
||||
## Jan 8, 2020
|
||||
|
||||
- https://github.com/dotnet/csharplang/pull/3035 Unconstrained type parameter annotation: `T??` (Chuck)
|
||||
- https://github.com/dotnet/csharplang/issues/2608 module initializers (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2910 base(T) (Neal)
|
||||
[C# Language Design Notes for Jan 8, 2020](LDM-2020-01-08.md)
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Jan 6, 2020
|
||||
|
||||
- Irksome nullable issues to revisit (Jared)
|
||||
- heed attribute info inside method bodies
|
||||
- consider special-casing `Task<T>` and `ValueTask<T>` for null covariance
|
||||
- https://github.com/dotnet/csharplang/projects/4#column-4649189 Triage recently championed features
|
||||
- https://github.com/dotnet/csharplang/issues/2844 Covariant Return Types (Neal)
|
||||
[C# Language Design Notes for Jan 6, 2020](LDM-2020-01-06.md)
|
||||
|
||||
# C# Language Design Notes for 2020
|
||||
|
||||
Overview of meetings and agendas for 2020
|
||||
1. Use attribute info inside method bodies
|
||||
1. Making Task-like types covariant for nullability
|
||||
1. Casting to non-nullable reference type
|
||||
1. Triage
|
||||
|
|
BIN
meetings/2020/Required_Properties_2020_09_16.pdf
Normal file
BIN
meetings/2020/Required_Properties_2020_09_16.pdf
Normal file
Binary file not shown.
BIN
meetings/2020/delectable_tea_2020_07_27.png
Normal file
BIN
meetings/2020/delectable_tea_2020_07_27.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 MiB |
48
meetings/2021/LDM-2021-01-05.md
Normal file
48
meetings/2021/LDM-2021-01-05.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# C# Language Design Meeting for Jan. 5th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
|
||||
## Quote of the Day:
|
||||
|
||||
- "I see a big tropical void where [redacted's] face was... It's so annoying"
|
||||
- "It's so cold here, it's 70 [F]"
|
||||
|
||||
## Discussion
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
Today, we looked at some of the details around this feature, specifically what should be supported before or after a top-level
|
||||
namespace declaration. The proposal as written allows only extern aliases, using directives, and global attributes. The proposal
|
||||
also does not allow multiple top-level namespaces: you can only have one in the file, and all following type declarations are
|
||||
considered to be part of that namespace. The debate, therefore, centers on whether we allow multiple of these declarations in a
|
||||
file, what they would mean in that case, and whether we allow a top-level namespace at the same file as top-level statements.
|
||||
|
||||
In many ways, this seems like a style question. Syntactically, regardless of whether allow these concepts to be mixed/duplicated
|
||||
in a single file in the formal grammar, the compiler will have to implement rules for what this means in order to provide a good
|
||||
IDE experience. There is potential value is allowing this to be flexible, as we generally do not take strong stances on syntax
|
||||
formatting guidelines beyond the default rules shipped with Roslyn, and those are very customizable to allow users to decide
|
||||
whether to allow them or not (`var` vs explicit type has 3 major doctrines, for example). By allowing all forms here, we would
|
||||
let users decide what is preferred to them and what is not.
|
||||
|
||||
That being said, however, we have concerns that we even understand what code like this would do:
|
||||
```cs
|
||||
namespace X;
|
||||
class A {}
|
||||
namespace Y;
|
||||
class B {}
|
||||
```
|
||||
For this scenario, some people would expect these types to be `X.A` and `Y.B`, while others would expect them to be `X.A` and
|
||||
`X.Y.B`. We have additional concerns around how this type of code would read in the presence of top-level statements, and
|
||||
whether there would be enough visual contrast between the end of the top-level statements, the namespace, and then types under
|
||||
the namespace, or whether that would be confusing to read. If we restrict the usage now, nothing would stop us from loosening
|
||||
restrictions in a later language version if we discover that we were too restrictive initially, but if we let the genie out of
|
||||
the bottle now, we can never put it back in.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
No conclusion today. We're largely split between these two extremes, allowing everything or allowing nothing. We'll take this
|
||||
back up again soon to finish debate and settle on a conclusion.
|
65
meetings/2021/LDM-2021-01-11.md
Normal file
65
meetings/2021/LDM-2021-01-11.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
# C# Language Design Meeting for Jan. 11th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required properties simple form](#required-properties-simple-form)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "You didn't want to hear me say um anyway... So, um"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required properties simple form
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4209#discussioncomment-275575
|
||||
|
||||
Today, we took a look at an extension to the existing required properties proposal, that proposed a syntax form
|
||||
that simplifies the base case of the proposal to remove complexity for what we believe is the 80% use case. This
|
||||
form adds a `require` modifier on a property definition. These requirements and then automatically added to the
|
||||
implicit parameterless constructor of a type, if present, and can be added to explicit constructors with
|
||||
`require default`, in the same place as the `init` lists of the last proposal.
|
||||
|
||||
First, we discussed the syntax in the proposal, and potential alternatives. We like the move to put a modifier
|
||||
on properties and fields as it makes implicit constructor scenarios much simpler, but something still feels off
|
||||
about the full-blown form of this syntax, with `require { Property, List }`. We could draw on type parameter
|
||||
constraint clauses, and a rough first attempt looks promising:
|
||||
|
||||
```cs
|
||||
public Person() require FirstName, LastName
|
||||
{
|
||||
}
|
||||
|
||||
public Student() require ID
|
||||
: base()
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
The proposed ability to assign to properties in the require list might be either odd or outright impossible here,
|
||||
depending on potential syntactic ambiguities we haven't thought of yet, but the syntax immediately feels more
|
||||
like C# than the previous curly-brace version.
|
||||
|
||||
Where we spent the bulk of meeting, however, was on how implicit we can make the default parameter list. We
|
||||
had immediate pushback on `require default` even being necessary on constructors: why can't we just infer that
|
||||
for all constructors in a type, and then have a syntax for removing all requirements? There's a feeling that
|
||||
`require default` is just busywork, and the compiler should just infer the defaults from the properties and
|
||||
fields in the type that are marked `require`. Some proposals for the ability to remove all requirements are
|
||||
`require none` and `require default = _`. We also considered a version of the proposal that goes even further,
|
||||
that doesn't allow constructors to `require` additional items: you mark a property or field as required, then
|
||||
remove requirements in the constructor itself. In this model, constructors would be unable to add new requirements,
|
||||
which does remove some potential scenarios, but could simplify the feature significantly. Roughly speaking, the
|
||||
three versions of the proposal can be summarized as follows:
|
||||
|
||||
1. Only implicit constructors get implicit require lists.
|
||||
2. All constructors get implicit require lists, and can add requirements of their own:
|
||||
1. If the constructor calls base in some manner (including implicit calls to the `object` constructor), that
|
||||
list is all the fields and properties in the type that are marked require.
|
||||
2. If the constructor calls another constructor on `this`, then it simply advertises chaining to that
|
||||
constructor, potentially removing some requirements if it takes care of them in its body.
|
||||
3. All constructors get implicit require lists, and cannot add to them. They can only remove them, and there
|
||||
is no `require` syntax. This version will need a new syntax for removing requirements, but that will likely
|
||||
be much simpler than the full `require` clause and need less user education.
|
||||
|
||||
After a read of the room, we're interested in seeing where proposal 3 goes. We'll work on fleshing that out
|
||||
with examples and bring it back to LDM soon.
|
84
meetings/2021/LDM-2021-01-13.md
Normal file
84
meetings/2021/LDM-2021-01-13.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# C# Language Design Meeting for Jan. 13th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Global usings](#global-usings)
|
||||
2. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "You're not yelling at me. You're just wrong."
|
||||
- "All language rules are arbitrary. Some are just more arbitrary than others."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Global usings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
Today, we started by looking at a long-standing request in various forms: the ability to create using directives that apply
|
||||
to an entire project. This is of particular importance now as we solidify our work in relation to the .NET 6 themes, especially
|
||||
around both beginner scenarios and general ease-of-use. A well-studied problem in teaching a programming language is avoiding
|
||||
cognitive load, and usings are an ever-present cognitive load in C#, even in simple "Hello, World!" style applications. Either
|
||||
the teacher says "Ignore the `using` thing for now" or they say "Ignore what the `.`s mean for now", with no ability to hold
|
||||
off on introducing them. That's not to say teaching `using` isn't necessary, and necessary early in the teaching process; merely
|
||||
that delaying that introduction from the first second of seeing C# to a day or week into the curriculum can be very helpful in
|
||||
avoiding overloading newcomers and scaring them off.
|
||||
|
||||
While it's an important scenario, beginners aren't the only driving motivation here. .NET 6 is looking at making both beginner
|
||||
and general scenarios better, and there's an argument that this will help general scenarios as well. Large-sized projects such
|
||||
as dotnet/roslyn are the exception, not the rule; most .NET projects are smaller and don't have nearly so many moving and
|
||||
interacting pieces. `using` boilerplate has a bigger impact on these projects, particularly as they tend to use many frameworks
|
||||
and a custom project SDK (such as ASP.NET). That custom SDK, combined with a feature to allow the SDK to specify global usings
|
||||
in some manner, can help ease these scenarios and remove unnecessary lines from most files in such solutions. Larger projects
|
||||
like Roslyn may never use this feature, but Roslyn and projects like it are not the projects that much of our users are actually
|
||||
writing.
|
||||
|
||||
Broadly, there are two possible approaches to this type of feature: CLI flags, specifiable for actual users via the project file,
|
||||
and a syntax form that allows a user to specify a using should apply to all files. We have an existing proposal for the former
|
||||
approach, 3428 (linked above), and some spitball ideas for what the latter could look like (perhaps something like
|
||||
`global using System;`). Both have advantages:
|
||||
|
||||
* If these are specified via command line flags, then there is one place to go looking for them: the project file. A syntax form
|
||||
would be potentially able to be spread out among multiple files. It is would be possible to spread these out across multiple
|
||||
props files if users wanted to, but the types of projects that use these features are likely rarer than the types of projects
|
||||
that use multiple C# files. Tooling could certainly help here, such as creating a new node in the project explorer to list all
|
||||
the global usings for a project, but we do still need to consider cases where code is not viewed in an IDE such as on GitHub.
|
||||
* We have a number of long-standing requests for having global using aliases. While these can be accomplished via the CLI flag
|
||||
proposal, it would be significantly easier and more accessible to users if they had a syntax form of doing so.
|
||||
* A syntax form would allow participation from source generators. We're somewhat split on whether that's a good thing or not.
|
||||
* A syntax form might be a barrier to potential abilities to do things like `dotnet run csfile` in the future: where would the
|
||||
syntax form live? An ethereal temp file, or a hardcoded part of the SDK?
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We seem to have unanimous agreement here that the design space is interesting, and we would like a feature to address the issues
|
||||
discussed above. We're much more split on the potential approaches, however, and need to explore the space more in depth.
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
We're picking back up on the discussion from [last week](LDM-2021-01-05.md#file-scoped-namespaces), which is around how
|
||||
restrictive we should be in permitted mixing and matching of multiple namespace statements and combination with traditional
|
||||
namespace directives. An important point to acknowledge is that, regardless of what decision we make here, Roslyn is going to
|
||||
have to do something to understand what multiple namespace directives in a file means because it will encounter that code at
|
||||
some point, regardless of whether it's valid or not, and will have to do its level best to make a guess as to what the user
|
||||
meant. There is a big difference between a compiler trying to make as much sense as it can of invalid code and the language
|
||||
having actual rules for the scenario, though. The scenario we're targeting specifically is files with one namespace in them
|
||||
(and most often, one type as well), and these scenarios make up roughly 99.8% of C# syntax files that lived on one LDT-member's
|
||||
computer. This includes the Roslyn codebase, which has several of these types of files specifically for the purposes of
|
||||
testing that we handle the scenario correctly. Measuring an even broader set of millions of C# files on GitHub shows literally
|
||||
99.99% of files have just one namespace in them.
|
||||
|
||||
We also briefly discussed the interaction with top-level statements. On the one hand, we're concerned about the readability of
|
||||
combining these things, and that the namespace statement would be too easily missed. On the other hand, having just finished
|
||||
talking about beginner scenarios, it seems like it might be annoying that beginners couldn't be introduced to the simple form
|
||||
until they start splitting things apart into multiple classes. Users will likely police themselves here if it doesn't read well,
|
||||
and maybe restricting it is just adding an arbitrary restriction.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We allow one and only one file-scoped namespace per file. You cannot combine a file-scoped namespace with a traditional
|
||||
namespace directive in the same file. We did not reach a conclusion on the combination with top-level statements and will
|
||||
pick that up again soon.
|
117
meetings/2021/LDM-2021-01-27.md
Normal file
117
meetings/2021/LDM-2021-01-27.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# C# Language Design Meeting for Jan. 27th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Init-only access on conversion on `this`](#init-only-access-on-conversion-on-this)
|
||||
2. [Record structs](#record-structs)
|
||||
1. [Copy constructors and Clone methods](#copy-constructors-and-clone)
|
||||
2. [`PrintMembers`](#printmembers)
|
||||
3. [Implemented equality algorithms](#implemented-equality-algorithms)
|
||||
4. [Field initializers](#field-initializers)
|
||||
5. [GetHashcode determinism](#gethashcode-determinism)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "You don't see the gazillion tabs I have in my other window... It's actually mostly stackoverflow posts on how to use System.IO.Pipelines."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Init-only access on conversion on `this`
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/50053
|
||||
|
||||
We have 3 options on this issue, centered around how whether we want to make a change and, if so, how far do we want to take it.
|
||||
|
||||
1. Change nothing. The scenario remains an error.
|
||||
2. Allow unconditional casts.
|
||||
3. Allow `as` casts as well.
|
||||
|
||||
We feel that this case is pretty pathological, and we have trouble coming up with real-world examples of APIs that would need to
|
||||
both hide a public member from a base type and initialize it in the constructor to some value. It would also be odd to allow it
|
||||
in the constructor while not having a form of initializing the property from an object initializer, which is the new thing that
|
||||
`init` enables over `set` methods. If we continue seeing a need to name hidden members, perhaps we can come up with a feature that
|
||||
generally allows that, as opposed to solving one particular case in constructors.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with 1. The proposal is rejected.
|
||||
|
||||
### Record structs
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4334
|
||||
|
||||
#### Copy constructors and Clone methods
|
||||
|
||||
We're revisiting the decision made the last time we talked about record structs. In that meeting, we decided to disallow record
|
||||
structs from defining customized `with` semantics, due to concerns over how such structs would behave in generic contexts when
|
||||
constrained to `where T : struct`. If we do disallow this customization, do we need to disallow methods named Clone as well?
|
||||
And should we also disallow copy constructors? In looking at the questions here, we spitballed some potential ways that we could
|
||||
allow customized copies and still allow record structs to be used in generics constrained to `where T : struct`, potentially by
|
||||
introducing a new interface in the BCL. A `with` on a struct type param would check for an implementation of that interface and
|
||||
call that, rather than blindly emitting a `dup` instruction as our original intention was. We think it's an interesting idea and
|
||||
want to pull it into a complete proposal, so we're holding off on making any decisions about allowed and disallowed members in
|
||||
record structs related to copying for now.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
On hold.
|
||||
|
||||
#### `PrintMembers`
|
||||
|
||||
A question was raised during initial review of the specification for record structs on whether we need to keep `PrintMembers`.
|
||||
Struct types don't have inheritance, so we could theoretically simplify that to just `ToString()` for this case. However, we
|
||||
think that there is value in minimizing the differences between record structs and record classes, so conversion between them
|
||||
is as painless as possible. Since users can provide their own `PrintMembers` method with their own semantics, removing it
|
||||
potentially introduces friction in making such a change.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Keep `PrintMembers`. The behavior will be the same as in record classes.
|
||||
|
||||
#### Implemented equality algorithms
|
||||
|
||||
During review, a question was raised about our original decision here with respect to generating the actual equality implementation
|
||||
for record structs. Originally, we had decided to not generate a new `Equals(object)` method, and have making a struct a record
|
||||
be solely about adding new surface area for the same functionality. Instead, we'd work with the runtime to make the equality
|
||||
generation better for all struct types. While we still want to pursue this angle as well, after discussion we decided that this
|
||||
would be another friction point between record classes and record structs, and could potentially have negative consequences if
|
||||
we don't have time to ship better runtime equality for structs in .NET 6, as many scenarios would then just need to turn around
|
||||
and implement equality themselves. In the future, if we do get the better runtime-generated equality, that could be added as a
|
||||
feature flag to `System.Runtime.CompilerServices.RuntimeFeature`, and we can inform the generation of equality based on the
|
||||
presence of the flag.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will generate equality methods in the same manner as proposed in the record struct specification proposal.
|
||||
|
||||
#### Field initializers
|
||||
|
||||
The question here is whether we can allow field initializers in structs that have a primary constructor with more than zero
|
||||
parameters. The immediate followup to that question, of course, is can we just finally allow parameterless constructors for
|
||||
structs in general, and then field initializers just work for all of them? We're still interested in doing this: the
|
||||
`Activator.CreateInstance` bug was in a version of the framework that is long out of support at this point, and we have universal
|
||||
agreement behind the idea. The last time we talked about the feature we took a look at non-defaultable value types in general,
|
||||
and while there are interesting ideas in there, we don't think we need to block parameterless constructors on it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Let's dig up the proposal from when we did parameterless struct constructors last and get it done, then this question becomes
|
||||
moot.
|
||||
|
||||
#### GetHashcode Determinism in `Combine`
|
||||
|
||||
The record class specification, and the record struct specification, states:
|
||||
|
||||
> The synthesized override of `GetHashCode()` returns an `int` result of a deterministic function combining the values of
|
||||
> `System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)` for each instance field `fieldN` with `TN` being
|
||||
> the type of `fieldN`.
|
||||
|
||||
We're not precise on the semantics of "a deterministic function combining the values" here, and the question is whether we should
|
||||
be more precise about the semantics of that. After discussion, we believe we're fine with the wording. It does not promise
|
||||
determinism across boundaries such as different executions of the same program or running the same program on different versions
|
||||
of the runtime, which are not guarantees we want to make.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Fine as is.
|
||||
|
78
meetings/2021/LDM-2021-02-03.md
Normal file
78
meetings/2021/LDM-2021-02-03.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
# C# Language Design Meeting for Feb 3rd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [List patterns on `IEnumerable`](#list-patterns-on-ienumerable)
|
||||
2. [Global usings](#global-usings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "If the teacher doesn't show up, class is dismissed, right?" "Who is the teacher in this scenario?"
|
||||
|
||||
## Discussion
|
||||
|
||||
### List patterns on `IEnumerable`
|
||||
|
||||
https://github.com/alrz/csharplang/blob/list-patterns/proposals/list-patterns.md
|
||||
|
||||
Today, we discussed what the behavior of list patterns should be for `IEnumerable`, and specifically how much of list patterns
|
||||
should be able to operate on enumerables. There are multiple competing factors here that we need to take into consideration.
|
||||
|
||||
1. `IEnumerable`s can be infinite. If a user were to attempt to match the count of such an enumerable, their code hangs.
|
||||
2. `IEnumerable`s can be expensive. Likely more common than the infinite case, enumerable can be a DB query that needs to run
|
||||
off to some data server in the cloud when queried. We absolutely do not want to enumerate these multiple times.
|
||||
3. We do not want to introduce a new pitfall of "Oh, you're in a hot loop, remove that pattern match because it'll be slower
|
||||
than checking the pattern by hand".
|
||||
|
||||
All of that said, we do think that list patterns on enumerables are useful. While this can be domain specific, efficient enumeration
|
||||
of enumerables is relatively boilerplate code and with some smart dependence on framework APIs, we think there is a path forward.
|
||||
For example, the runtime just approved a new API for [`TryGetNonEnumeratedCount`](https://github.com/dotnet/runtime/issues/27183),
|
||||
and in order to make the pattern fast we could attempt to use it, then fall back to a state-machine-based approach if the collection
|
||||
must be iterated. This would give us the best of both worlds: If the enumerable is actually backed by a concrete list type, we don't
|
||||
need to do any enumeration of the enumerable to check the length pattern. If it's not, we can fall back to the state machine, which
|
||||
can do a more efficient enumeration while checking subpatterns than we could expose as an API from the BCL.
|
||||
|
||||
For the state machine fallback, we want to be as efficient as possible. This means not enumerating twice, and bailing out as soon
|
||||
as possible. So, the pattern `enumerable [< 6] { 1, 2, 3, .., 10 }` can immediately return false if it gets to more than 6 elements,
|
||||
or if any of the first 3 elements don't match the supplied patterns.
|
||||
|
||||
Finally, on the topic of potentially infinite or expensive enumerations, they are an existing problem today. The BCL exposes a `Count`
|
||||
API, and if you call it on a Fibonacci sequence generator, your program will hang. Enumerating db calls is expensive, regardless
|
||||
of whether we provide a new, more succinct form or not. In these cases, users generally know what they're working with: it's not a
|
||||
surprise that they have an infinite enumerable, they've very likely already done a `Take` or some other subsequence mechanism if they're
|
||||
looking for "the last element from the end". By having these patterns, we simply allow these users to take advantage of a generation
|
||||
strategy that's as efficient as they could write by hand, with much clearer intent. As long as the enumeration has a specific pattern
|
||||
that users can reason about, it's an overall win.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll proceed with making a detailed specification on how `IEnumerable` will be pattern matched against. We're ok with taking advantage
|
||||
of BCL APIs here, including `TryGetNonEnumeratedCount`, and are comfortable working with the BCL team to add new APIs if existing ones
|
||||
don't prove complete enough for our purposes.
|
||||
|
||||
### Global usings
|
||||
|
||||
We started this by looking at a prototype of how ASP.NET is looking to reduce ceremony in their templates with a framework called
|
||||
Feather, which can be seen [here](https://github.com/featherhttp/framework). The hello world for this code is 12 lines long: 6 lines
|
||||
of actual code, 3 newlines, and 3 lines of usings. As apps get more complicated, these usings tend to grow quite quickly, and they're
|
||||
all for the types of things that often boil down to "I want to use the async feature from C# 5, LINQ from C# 3, generic collections
|
||||
from C# 2, and I want to build an ASP.NET application". This hints at a related, but orthogonal, using feature: recursive usings. For
|
||||
example, `using System.*` would bring in all namespaces under `System`, or `using Microsoft.AspNetCore.*` would bring in all namespaces
|
||||
under `Microsoft.AspNetCore`. However, such a feature wouldn't really solve the issue in question here, which is "how can specifying
|
||||
the SDK in use ensure that I get the ability to use the features of that SDK by default?"
|
||||
|
||||
We have 2 general approaches here: use the project file as the place where implicit usings go, or allow a source file to include them.
|
||||
Both approaches have several pros and cons. In a project file works more natively for an SDK, as they can just define a property. The
|
||||
SDK does define an AssemblyVersion.cs today, but this feature is potentially more complicated than that. The project file is also
|
||||
where we tend to put these types of global controls, like nullable or checked. On the other hand, project files are very hard to tool,
|
||||
as MSBuild is a complicated beast that can do arbitrary things. Artificial restrictions on the feature, like requiring that it appear
|
||||
directly in the project file and not in some other targets file, severely limits the usefulness of the feature across solutions. Source
|
||||
files as the solution provide an easily-toolable experience that feels more C#-native, but potentially encourages these usings to be
|
||||
spread out in many locations. Razor has a `_ViewImports.cshtml` file that handles this problem for Razor files, but we don't think this
|
||||
maps well to the solutions we're discussing for C#: it only allows the one file, and is in some ways the "project file" for the rest
|
||||
of the cshtml files in the solution as it provides things like the namespace of the rest of the pages.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're split right down the middle here between project file and C# files. We'll revisit this again very shortly to try and make
|
||||
progress on the feature.
|
118
meetings/2021/LDM-2021-02-08.md
Normal file
118
meetings/2021/LDM-2021-02-08.md
Normal file
|
@ -0,0 +1,118 @@
|
|||
# C# Language Design Meeting for Feb 8th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Virtual statics in interfaces
|
||||
1. [Syntax Clashes](#syntax-clashes)
|
||||
2. [Self-applicability as a constraint](#self-applicability-as-a-constraint)
|
||||
3. [Relaxed operator operand types](#relaxed-operator-operand-types)
|
||||
4. [Constructors](#constructor)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "If you need to kick yourself I see that [redacted] has a foot in the chat you can kick yourself with"
|
||||
- "Are we at the point where we derail your meeting with other proposals?"
|
||||
- "It's the classic, noble art of retconning"
|
||||
|
||||
## Discussion
|
||||
|
||||
Today, we want to take a look at a few top-level design decisions for virtual statics in interfaces that will drive further design and implementation
|
||||
decisions for this feature.
|
||||
|
||||
### Syntax Clashes
|
||||
|
||||
C# 8 brought 2 new features to interfaces: default members, and non-virtual static members. This sets up a clash between static virtual members and
|
||||
static non-virtual members. In pre-C# 8, `interface` members were always virtual and abstract. C# 8 blurred the line here: private and static members
|
||||
in interfaces are non-virtual. For private members, this makes perfect sense, as the concept of a virtual private method is an oxymoron: if you can't
|
||||
see it, you can't override it. For statics, it's a bit more unfortunate because we now have inconsistency in the default-virtualness of members in
|
||||
the interface. We have a few options for addressing this inconsistency:
|
||||
|
||||
1. Accept life as it is. We'd require `abstract` and/or `virtual` on static members to mark them as such. There is some benefit here: `static` has
|
||||
meant something for 21 years in C#, and that is not `virtual`. Requiring a modifier means this does not change.
|
||||
2. Break the world. Make static members in interfaces mean static virtual by default. This gets us consistency, but breaks consumers in some fashion
|
||||
and/or introduces multiple dialects of C# to support.
|
||||
3. We could introduce a bifurcation in interfaces with a `virtual` modifier on the `interface` declaration itself. When marked with `virtual`, `static`
|
||||
members of the interface are automatically `virtual` by default. This is very not C#-like: we require `static` on all members of `static class`es, why
|
||||
would `virtual interface`s be any different here? And how would you define the existing non-`virtual` members in such an interface?
|
||||
|
||||
Options 2 and 3 also have a question of how they will apply to class members. Due to the size of the changes required we may have to split virtual
|
||||
static members, shipping with just interfaces in the first iteration and adding class types at a later point. However, we need to make sure that we
|
||||
design the language side of these changes together, so when class virtual statics are added they don't feel like an afterthought. The second and third
|
||||
proposals would likely need to have the first proposal for the class version of the feature anyway. While it would be consistent with instance members,
|
||||
neither of them would totally eliminate the needs to apply the modifiers to interface members.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will require `abstract` or `virtual` be applied to a virtual static member. We will also look at allowing these modifiers for instance interface
|
||||
methods, even though they are redundant, much like we allow `public` on members today.
|
||||
|
||||
### Self-applicability as a constraint
|
||||
|
||||
`abstract` static members introduce an interesting scenario in which an interface is no longer valid as a substitute for a type parameter constrained
|
||||
to that interface type. Consider this scenario:
|
||||
|
||||
```cs
|
||||
interface IHasStatics
|
||||
{
|
||||
abstract static int GetValue(); // No implementation of GetValue here
|
||||
}
|
||||
|
||||
class C : IHasStatics
|
||||
{
|
||||
static int GetValue() => 0;
|
||||
}
|
||||
|
||||
void UseStatics<T>() where T : IHasStatics
|
||||
{
|
||||
int v = T.GetValue();
|
||||
}
|
||||
|
||||
UseStatics<C>(); // C satisfies constraint
|
||||
UseStatics<IHasStatics>(); // Error: IHasStatics doesn't satisfy constraint
|
||||
```
|
||||
|
||||
The main question here is what do we do about this? We have 2 paths:
|
||||
|
||||
1. Forbid interfaces with an abstract static from satisfying a constraint on itself.
|
||||
2. Forbid access to static virtuals with a type parameter unless you have an additional constraint like `concrete`.
|
||||
|
||||
Option 2 seems weird here. Why would a user have constrained to a type that implements an interface, rather than just taking the interface, unless
|
||||
they wanted to use these methods? Yes, adding an `abstract static` method to an interface where one does not exist today would be a breaking change
|
||||
for consumers, but that's nothing new: that's why we added DIMs in the first place, and it would continue to be possible to avoid the break by providing
|
||||
a default method body for the virtual method, instead of making it abstract.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We choose option 1.
|
||||
|
||||
### Relaxed operator operand types
|
||||
|
||||
Today, C# requires that at least one of the operand-types of a user-defined operator be the current type. This breaks down with user-defined virtual
|
||||
operators in interfaces, however, as the type in the operator won't be the current type, it will be some type derived from the current type. Here,
|
||||
we naturally look at self types as a possible option. We are concerned with the amount of work that self-types will require, however, and aren't sure
|
||||
that we want to tie the shipping of virtual statics to the need for self types (and any other associated-type feature). We also need to make sure that
|
||||
we relax operators enough, and define BCL-native interfaces in a way, such that asymmetric types are representable. For example, a matrix type would
|
||||
want to be able to add a matrix and a numeric type, and return a matrix. Or `byte`'s `+` operator, which does not return a `byte` today. Given that,
|
||||
we think it is alright to ship this feature and define a set of operator interfaces without the self type, as we would likely be forced to not use it
|
||||
in the general interfaces anyway to keep them flexible enough for all our use cases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're ok with relaxing the constraints as much as we need to here. We won't block on self types being in the language.
|
||||
|
||||
### Constructors
|
||||
|
||||
Finally, we looked at allowing or disallowing constructors as virtual in interfaces. This is an interesting area: either derived types would be required
|
||||
to provide a constructor that matches the interface, or derived types would be allowed to not implement interfaces that their base types do. The feature
|
||||
itself is a parallel to regular static methods; in order to properly describe it in terms of static methods, you'd need to have a self type, which is
|
||||
what makes it hard to describe in today's C# terms in the first place. Adding this also brings in the question of what do we do about `new()`? This may
|
||||
be an area where we should prefer a structural approach over a nominal approach, or add a form of type classes to the language: if we were to effectively
|
||||
deprecate `new()`, that would mean that every type that has a parameterless constructor would need to implement an `IHasConstructor` interface instead.
|
||||
And we would need to have infinite variations of that interface, and add them to every type in the BCL. This would be a serious issue, both in terms of
|
||||
sheer surface area required and in terms of effect on type loads and runtime performance penalties for thousands and thousands of new types.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will not have virtual constructors for now. We think that if we add type classes (and allow implementing a type class on a type the user doesn't own),
|
||||
that will be a better place for them. If we want to improve the `new()` constraint in the mean time, we can look at a more structural form, and users
|
||||
can work around the lack of additional constraints for now by using a static virtual.
|
168
meetings/2021/LDM-2021-02-10.md
Normal file
168
meetings/2021/LDM-2021-02-10.md
Normal file
|
@ -0,0 +1,168 @@
|
|||
# C# Language Design Meeting for Feb 10th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Follow up on record equality](#follow-up-on-record-equality)
|
||||
2. [Namespace directives in top-level programs](#namespace-directives-in-top-level-programs)
|
||||
3. [Global usings](#global-usings)
|
||||
4. [Triage](#triage)
|
||||
1. [Nominal And Collection Deconstruction](#nominal-and-collection-deconstruction)
|
||||
2. [Sealed record ToString](#sealed-record-ToString)
|
||||
3. [`using` aliases for tuple syntax](#using-aliases-for-tuple-syntax)
|
||||
4. [Raw string literals](#raw-string-literals)
|
||||
5. [Allow `var` variables to be used in a `nameof` in their initializers](#allow-var-variables-to-be-used-in-a-nameof-in-their-initializers)
|
||||
6. [First-class native integer support](#first-class-native-integer-support)
|
||||
7. [Extended property patterns](#extended-property-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "That's 3 issues in 8 minutes." "That's an LDM record!"
|
||||
- "Back to serious stuff. Raw string literals. Because raw is better for digestion"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Follow up on record equality
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/39#issuecomment-678097433
|
||||
https://github.com/dotnet/csharplang/discussions/4411
|
||||
|
||||
Back when the linked comment on #39 was raised, we had an internal email chain discussing changing the implementation of `==` to move the
|
||||
reference equality short-circuit down to the strongly-typed `Equals` implementation. We came to the conclusion that instead of moving the
|
||||
check we should duplicate it, to ensure that `==` still behaves correctly for `null`, but we never followed up on that conclusion. Today,
|
||||
we confirmed that we do indeed want to do that, and update the C# compiler to generate this better version of equality.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll do this. https://github.com/dotnet/roslyn/issues/51136 tracks following up on that work.
|
||||
|
||||
### Namespace directives in top-level programs
|
||||
|
||||
Our last discussion on the namespace directive had one open question left: should we allow them in combination with top-level statements?
|
||||
We had an internal email chain on this topic since then, and came to the conclusion that we should disallow this combination. There's a
|
||||
couple reasons for this:
|
||||
|
||||
1. We think the namespace directive is confusing here. It looks like a statement, but the rest of the statements on the top-level don't
|
||||
introduce a new scope.
|
||||
2. It is easier to remove an error later if we decide that it's too restrictive. If we allow it, we'd have to support it forever.
|
||||
3. Users might expect to be able to put a namespace _before_ those top-level statements and change the namespace the implicit `Main` is
|
||||
generated into.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Decision upheld. Top-level statements are not allowed in combination with file-scoped namespace directives.
|
||||
|
||||
### Global usings
|
||||
|
||||
Our last discussion on global usings had us split right down the middle on whether to make global usings a C# syntax feature, or a
|
||||
command-line switch to the compiler, with a razor-thin majority leaning to C# syntax. In order to make progress here to begin
|
||||
implementation, we again took an internal email chain to discuss this further. This conversation drew a few conclusions:
|
||||
|
||||
* Neither a a command-line switch nor C# syntax would prevent a source-generator from providing their own set of usings, but the switch
|
||||
would prevent the source-generator from _dynamically_ providing this, it would have to be predefined in props/targets file in the
|
||||
generator nuget package.
|
||||
* We really need to be considering the experience when using `dotnet`, not `csc`. The SDK passes 182 parameters to a build of a simple
|
||||
hello world application today. It's very unrealistic to base scenarios on calling `csc` directly.
|
||||
* For the base templates, such as as a console app or a default ASP.NET application, this choice doesn't really affect the files the
|
||||
templates drop down. In either case, the usings will be hidden. For more complicated templates, this choice changes whether the template
|
||||
drops a `GlobalUsings.cs` file, or fills in a property in the template csproj. In either case, the template has really moved beyond a
|
||||
single-file program, so the difference isn't huge.
|
||||
* We've had a number of requests over the years for global aliases, and a C# syntax will fit in nicely with such a feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Given the need to start implementing here and the thin majority that prefer C# syntax, we will start investigation and implementation
|
||||
on the C# syntax version, with a speclet forthcoming later this week.
|
||||
|
||||
### Triage
|
||||
|
||||
#### Nominal and Collection Deconstruction
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4082
|
||||
|
||||
This feature will add symmetry with tuple patterns/deconstruction, which is desirable. There is some potential parsing challenges, as
|
||||
several of the examples look like a block with a labelled statement inside it and will need some tricky work to ensure disambiguation
|
||||
works.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
To the backlog. We like the idea and think there's a way to get it into a form we like, but can't commit to it right now.
|
||||
|
||||
#### Sealed record ToString
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4174
|
||||
|
||||
This is a simple enough change and we've had a few complaints about the behavior and inability to seal. As an alternative, we could
|
||||
design a parallel ToString implementation for records that the base type in a hierarchy will call into, which would be a minor behavior
|
||||
change from C# 9 as released. However, this would be complicated, and `sealed` is the keyword to tell consumers "No really, this is the
|
||||
final version of this behavior, you can't change it."
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll put this in Any Time to promote community contributions for this small feature, but if no one picks it up we will likely put in a
|
||||
bit of effort to get it into C# 10.
|
||||
|
||||
#### `using` aliases for tuple syntax
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4284
|
||||
|
||||
The title of this issue is a bit of a misnomer, as the proposal actually extends to all types, not just tuples. We've also looked at a
|
||||
broader proposal recently in conjunction with the global using statement feature, which would allow things like
|
||||
`global using MyDictionary<TValue> = System.Collections.Generic.Dictionary<int, TValue>` and other similar work. It seems like we want
|
||||
to make some improvements in the alias and using area, and we should consider doing a subset of that larger proposal now in the same
|
||||
fashion that we've continuously shipped incremental updates for patterns.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set. We want to make a general using improvement push in 10, and have a goal of at least doing this much for
|
||||
alias improvements in this release.
|
||||
|
||||
#### Raw string literals
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4304
|
||||
|
||||
There are a number of potentially contentious design decisions in this area, but we intentionally tried to steer away from them today
|
||||
and limit to just the general concept. There are many scenarios for embedding other languages into C#: XML, JSON/Javascript, C#, and
|
||||
more. In many of these languages, " and ' are not interchangeable, so anything with a " in it is painful in C# today. We also thing that
|
||||
interpolation in these strings is important, as these embedded code scenarios are often used for templating. There is potential work around
|
||||
a way to specify what character sequence defines the interpolation holes, but we did not go into details or specifics here.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Into the Working Set. There's a bunch of design questions that we'll need to spend some time working on.
|
||||
|
||||
#### Allow `var` variables to be used in a `nameof` in their initializers
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4384
|
||||
|
||||
This is a minor convenience issue that has come up a few times for users, but has potential implementation nastiness with disambiguating
|
||||
whether `nameof` is a method or a `nameof_expression`. Given the minor benefit and the potential implementation concerns, we're concerned
|
||||
about doing this unless we decide to make `nameof` a non-conditional keyword.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. If we ever make the change to make `nameof` not a conditional keyword and can simplify the binding here, then we can bring this
|
||||
back, but until that point we will leave this as is.
|
||||
|
||||
|
||||
#### First-class native integer support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4385
|
||||
|
||||
As the language is specified today, compliant C# compilers have to emit certain overflow conversions and then roundtrip those conversions
|
||||
back to the original representation. This is inefficient and a small spec change will produce no observable semantic difference, while
|
||||
allowing the compiler to emit better code.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Accepted into the working set.
|
||||
|
||||
#### Extended property patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4394
|
||||
|
||||
This is something that patterns are very verbose about today, and it's a syntax we've previously discussed as a potential shorthand to
|
||||
reduce the boilerplate. We could also potentially extend this idea to object initializers, but don't want to tie that to this proposal.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Accepted into the working set.
|
86
meetings/2021/LDM-2021-02-22.md
Normal file
86
meetings/2021/LDM-2021-02-22.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# C# Language Design Meeting for Feb 22nd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Global `using`s](#global-usings)
|
||||
2. [`using` alias improvements](#using-alias-improvements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
* "You're muted" "I wanted to be muted"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Global `using`s
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md
|
||||
|
||||
Today we discussed the proposed syntax and restrictions on the current feature specification. As checked in today, the proposal
|
||||
puts `global` after the `using` directive, which could be potentially ambiguous and require complicated parsing logic, as `global`
|
||||
is a valid namespace identifier today. For example, this is valid code:
|
||||
|
||||
```cs
|
||||
// Is this a global using alias, or a top-level using variable declaration?
|
||||
using global myVar = Test.AGlobal;
|
||||
|
||||
class Test
|
||||
{
|
||||
public static global AGlobal = new global();
|
||||
}
|
||||
|
||||
class global : IDisposable { public void Dispose() {} }
|
||||
```
|
||||
|
||||
We could potentially resolve this in the same way we did for the `record` keyword, which is to forbid types be named `global` in
|
||||
C# 10. We haven't gotten pushback on this approach for `record` as a type name so far, which is positive. However, aside from the
|
||||
ambiguity, there are other reasons to view `global`-first as a good thing. `global` here is a modifier on the `using`, which C#
|
||||
generally puts before the declaration, not after. We also considered 2 separate but related questions:
|
||||
|
||||
1. Is `static` a similar modifier? Should it be allowed to come before the `using` as well?
|
||||
* After discussion, we don't believe so. `static` isn't a modifier on the `using`, it fundamentally changes what the meaning
|
||||
of the using is about.
|
||||
2. We already have existing modifiers for C# scopes: should we use `internal` as the keyword here?
|
||||
* Using `internal` begs the followup question: what does `public` on one of these `using`s mean? What about `private`? We're
|
||||
uncomfortable with the idea of treating these more like types than like macros. This starts to get into a very slippery slope
|
||||
of how we export aliases publicly, can additional constraints be added to aliases, and how does that interact with roles?
|
||||
|
||||
Finally, we discussed the proposed restrictions on the feature. We like them overall: if `global` and non-`global` `using`s can
|
||||
be interspersed, it could lead to user confusion as to whether regular `using` directives can affect `global` ones. It also helps
|
||||
set up a clearly-defined set of scopes. We add a new `global` scope that comes before all top-level `using` statements today, and
|
||||
their interactions mirror the interactions of regular `using`s statements with those nested in a namespace.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We put `global` before `using`, but the proposal is otherwise accepted as is.
|
||||
|
||||
### `using` alias improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/4452
|
||||
|
||||
Continuing with the theme of `using` improvements, we also discussed generalized improvements to `using` aliases to address a
|
||||
number of reoccuring complaints over the years with limitations in today's approach. Specifically, `using` directives today
|
||||
cannot reference predefined names like `string`, they can't have generic type arguments, they can't use tuple syntax, or use
|
||||
pointer types (including the C# 9 function pointer syntax). The current proposal allows all of these things, including aliases
|
||||
for partially-bound type parameters such as `using MyDictionary<T> = System.Collections.Generic.Dictionary<int, T>;`. When
|
||||
combined with the previous `global using` feature, enabling this would allow compilation-unit type aliases. Our remaining open
|
||||
questions here focus again on how we want to push `using`s forward in the future:
|
||||
|
||||
1. If we want to push `using` aliases as real types, then they need to have the ability to expression the same things all other
|
||||
types can, including constraints and variance. If we go this route, we could potentially have a future where you can introduce
|
||||
an alias that adds a _new_ constraint onto an existing type, such as an invariant `IEnumerable<T>` alias, or a dictionary that
|
||||
is constrained to only `struct`-type values.
|
||||
2. If we want to push `using` aliases as more macro-expansion, then the ability to expression constraints and variance is a
|
||||
confusing detriment. The type parameter will be substituted at expansion site and we'll error at that point if an illegal
|
||||
substitution is made.
|
||||
|
||||
Approach 1 has many followup questions that quickly start to slide into roles territory. Aliases can be used in public API, for
|
||||
example, so how would we advertise them publicly if the alias has added new constraints? Is it possible for users to introduce
|
||||
an opaque alias, such as aliasing `int` to `CustomerId` in such a way as there's no implicit conversion between them? Ultimately,
|
||||
we think that these problems are better solved by a discrete language feature such as roles, rather than as an expansion on
|
||||
`using` aliases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
`using` aliases will be treated more as macro expansions than as real types. We'll check substitutions for concrete types where
|
||||
we can (such as erroring in the alias itself for `using MyList = System.Collections.Generic.List<int*>;`), but for things we
|
||||
cannot check at the declaration site, we will error at the use site. There is no way to add constraints to a `using` alias.
|
55
meetings/2021/LDM-2021-02-24.md
Normal file
55
meetings/2021/LDM-2021-02-24.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
# C# Language Design Meeting for Feb 24th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Static abstract members in interfaces](#static-abstract-members-in-interfaces)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I'm not using the monoid word, I'm trying to make it relatable"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Static abstract members in interfaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4436
|
||||
|
||||
Today we went over the proposed specification for `static` abstract members. In order to scope the initial implementation, this proposal
|
||||
intentionally limits such members to _only_ `abstract` members, not `virtual` members. This is due to complexity in passing along the
|
||||
"instance" that the static is operating on. Consider this example:
|
||||
|
||||
```cs
|
||||
interface I
|
||||
{
|
||||
virtual static void I1() { I2(); }
|
||||
abstract static I2();
|
||||
}
|
||||
|
||||
class C : I
|
||||
{
|
||||
public static void I2() {}
|
||||
}
|
||||
|
||||
C.I1();
|
||||
// How does the runtime pass along the information that the type here is C,
|
||||
// and I.M1() should invoke C.I2()?
|
||||
```
|
||||
|
||||
Given the complexity of implementation of this scenario and the lack of current motivating examples, we will push this out for a later
|
||||
version unless our investigations of representing generic math end up needing it.
|
||||
|
||||
#### `==` and `!=` operators
|
||||
|
||||
These are restricted in interfaces today because interface types cannot implement `object.Equals(object other)` and `object.GetHashcode()`,
|
||||
which are integral to implementing the operators correctly. We can lift that restriction for `abstract static` members, as the implementing
|
||||
concrete type will then be required to provide implementations of `Equals(object other)` and `GetHashcode()`.
|
||||
|
||||
#### `sealed` on non-abstract members
|
||||
|
||||
We don't have any strong feelings on allowing `sealed` on non-virtual `static` interface members. It's fine to include in the proposal.
|
||||
|
||||
#### Conversion restrictions
|
||||
|
||||
As we implement a set of math interfaces, we'll come across parts of the current restrictions that make it impossible. We should be cautious
|
||||
here and only lift restrictions as much as is necessary to implement the target scenarios. We can review the rules in depth when they have
|
||||
been proven out by final usage.
|
93
meetings/2021/LDM-2021-03-01.md
Normal file
93
meetings/2021/LDM-2021-03-01.md
Normal file
|
@ -0,0 +1,93 @@
|
|||
# C# Language Design Meeting for March 1st, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Async method builder override](#async-method-builder-override)
|
||||
2. [Async exception filters](#async-exception-filters)
|
||||
3. [Interpolated string improvements](#interpolated-string-improvements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I checked my math twice before I spoke." "Well I did not."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Async method builder override
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1407
|
||||
|
||||
We first looked at a proposal to enable customization of the async state machine generated by the compiler to allow for additional
|
||||
customizability. Today, there is no way to customize the builder that the compiler emits for an async method except by introducing
|
||||
a new task-like type. This is a leaky abstraction that limits the ability of the BCL and other libraries to adopt pooling and other
|
||||
customizations where appropriate because they don't want to leak the implementation detail of the pooling to users of a method, but
|
||||
don't have another way to customize the builder.
|
||||
|
||||
This proposal has simple core, with multiple possible addendums that add more complexity but also more customizability to usages.
|
||||
After review, we believe the initial proposal is enough to cover our needs here, as additional arguments to a builder can be achieved
|
||||
by using additional builder types that have their own `Create` methods. This could be somewhat ugly for deeply-variable scenarios,
|
||||
but we believe this is enough of a corner case that we don't need to support it in the language, at least initially. If the need
|
||||
comes up for it in a future version, we can look at adding more customization then.
|
||||
|
||||
We also looked at ways to simplify the type/module level constructor. The proposal today suggests having a constructor that takes
|
||||
both the builder type and the task-like type, that would inform the compiler of when to apply the customized builder. This seems
|
||||
like mandated busy-work for the developer: the builder type returns an instance of this task-like type from a well-defined method,
|
||||
and we'd need to verify that the specified task-like type is compatible with the return of that method, so it seems like it would
|
||||
be easier for the user to just infer the applicable task-like type from the builder. There are some interesting scenarios around
|
||||
open generics here: how does the compiler perform substitution to arrive at the final builder type, and verify that the task-like
|
||||
return is correct? Some spec work will be needed to achieve this, but it should be possible, and should be overall worth it.
|
||||
|
||||
Another question is whether it's possible to go back to the default builder on a method if an attribute has been applied at a
|
||||
type/module level. This should be doable: all the standard builder types are public API, so all the user needs to know is what the
|
||||
default builder for a type actually is. This is also a fairly niche corner in an already niche scenario, so we don't think any
|
||||
additional sugar or "default" constructors are necessary.
|
||||
|
||||
Diagnostics will need to be reported on the use-site for issues involving generic substitutions. Builder generics will be able add
|
||||
new constraints on type parameters that the underlying task-like type doesn't have, so invalid uses need to be carefully handled
|
||||
and diagnostics reported to ensure a good user experience.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We unanimously like looking at a type inference-based approach and seeing if we can make it work.
|
||||
|
||||
### Async exception filters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4485
|
||||
|
||||
This is a proposal to address a pain-point in async contexts, where the state machine can catch exceptions and unwind the exception
|
||||
stack before user-code can catch the exception, which leads to bug reports and heap dumps that are missing the actual stack trace
|
||||
where an exception is thrown. Roslyn is a prime example of this, with lots of try/catch handlers sprinkled over the codebase in
|
||||
async contexts to be able to report heap dumps before the async machine catches and unwinds the stack. However, this is often a
|
||||
error-prone process: Roslyn receives a bug report with a missing stack, adds a new try/catch to ensure it gets a good bug report,
|
||||
then waits for a user to encounter the problem again. This proposal would allow the user to define a handler that gets called when
|
||||
the async state machine gets called back for an exception, allowing Roslyn and similar async applications to report take telemetry
|
||||
and other similar actions before stack unwinding. Similar to the first proposal today, it in many ways represents an aspect-oriented
|
||||
feature of customizing the async state machine emit in some way.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We like the idea of the proposal, but it needs to be fleshed out more. We'll look at a more complete proposal when it's ready.
|
||||
|
||||
### Interpolated string improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Finally today, we looked at a proposal on improving code gen and usage for interpolated string expressions. Most of this section of
|
||||
the meeting was spent going through the proposal itself, with not a lot of discussion on the individual points or open questions.
|
||||
Observations we made:
|
||||
|
||||
* We should consider making the `GetInterpolatedString` and `TryFormat` methods public only. This ensures that, like instance
|
||||
GetEnumerator() methods, there isn't potential confusion when one method is preferred in project A but another method is preferred
|
||||
in project B, even when all the types involved are otherwise identical.
|
||||
* We'll need to carefully consider how the `out` parameter affects local lifetime rules for `ref struct` builder types, which we expect
|
||||
to be the majority. Most of this should end up falling out, but careful verification will be needed.
|
||||
* The more likely compat issue is that a library introduces a public API that exposes one of these builder types, and an older compiler
|
||||
is fed that API and picks a different method. We don't see any real way to avoid this, nor do we think it's a deal breaker.
|
||||
* Even intentional, measured changes to better conversion from expression are scary, and this needs very careful review to ensure
|
||||
we're doing exactly what we want, and no more.
|
||||
* `TryFormat` short-circuiting can be quite powerful. There are plenty of logging scenarios where the user would not want to evaluate
|
||||
some expensive computation that we get around today by creating `Func`s and lazily-evaluating. This would solve that, but we need
|
||||
to make sure we're not creating an easy pitfall for users.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We like the direction of this proposal. We did not address the open questions in depth today, will need to come back to them soon.
|
170
meetings/2021/LDM-2021-03-03.md
Normal file
170
meetings/2021/LDM-2021-03-03.md
Normal file
|
@ -0,0 +1,170 @@
|
|||
# C# Language Design Meeting for March 3rd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Natural type for lambdas](#natural-type-for-lambdas)
|
||||
1. [Attributes](#attributes)
|
||||
2. [Return types](#return-types)
|
||||
3. [Natural delegate types](#natural-delegate-types)
|
||||
2. [Required members](#required-members)
|
||||
3. [Appendix: ASP.NET examples](#appendix-aspnet-examples)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I specifically avoided using the word to avoid settling on a pronunciation for everyone to get annoyed at."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Natural type for lambdas
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/lambda-improvements.md
|
||||
|
||||
Today we looked at a proposal around enhancing lambda expressions and method groups with a "natural type", which is helpful for several
|
||||
ASP.NET scenarios. Overall, the LDM has general support for making enhancements here: explaining how lambdas and method groups don't have
|
||||
natural types and why that means you can't use `var` or other similar scenarios has always been a pain point. Examples that show the
|
||||
ASP.NET scenarios are at the bottom of these notes in [Appendix: ASP.NET examples](#appendix-aspnet-examples).
|
||||
|
||||
#### Attributes
|
||||
|
||||
There are some important details around how exactly these attributes are emitted to the final assembly that need to be ironed out, and this
|
||||
likely will mean that we need to specify more about how lambdas themselves are emitted. This is not something we do today, so we need to make
|
||||
sure that we are cautious enough about this specification that we don't box ourselves out of future lambda optimizations we can make today.
|
||||
|
||||
Syntax-wise, there's a couple of potential issues. We need to make sure that a leading `[Attribute]` is never ambiguous as an indexer on a
|
||||
previous expression. If we require that the lambda is entirely parenthesized when an attribute is used it shouldn't be ambiguous, but
|
||||
parenthesizing lambdas can be a pain and isn't the prettiest thing in the world. There's an additional question of whether we should always
|
||||
require the arguments to a lambda to be parenthesized if there is a leading attribute. This code is visually ambiguous if unparenthesized,
|
||||
for example:
|
||||
|
||||
```cs
|
||||
[MyAttribute] x => x; // Does MyAttribute apply to the parameter or the entire lambda?
|
||||
```
|
||||
|
||||
Another syntax question is around the requirement to specify the parameter type when attributing a parameter. We have an open proposal to
|
||||
remove the requirement to specify the type when using `ref` or other parameter modifiers, so why add the requirement here? The main issue
|
||||
is around making sure that we can parse the lambda. We added the requirement for modifiers previously because we weren't sure if we could
|
||||
always parse the lambda. If we want to remove the requirement here as well, we need to make sure that we've done the work to ensure there
|
||||
are no parsing issues.
|
||||
|
||||
#### Return types
|
||||
|
||||
Parsing issues abound here as well. `:` is _very_ dangerous from a parsing perspective, potentially requiring a large amount of lookahead and
|
||||
recovery to figure out whether we're in a ternary or a lambda expression. Even if it does prove to always be unambiguous, we're not fans of
|
||||
the amount of effort that disambiguation will require. Some potential alternatives we discussed:
|
||||
|
||||
```
|
||||
() -> int { ... } // Potentially ambiguous with pointer derefs, but should be easier to disambiguate than the :
|
||||
(-> int) { ... } // Also possibly ambiguous, but again easier to disambiguate than :
|
||||
(return: int) { ... }
|
||||
```
|
||||
|
||||
We could also look into forms that put the return type on the left side of the lambda, which fits more into existing method declaration
|
||||
syntax today. However, lambdas are closer to `Func<T>` and function pointer types, and today both of those use the right-hand side for the
|
||||
return type, so using a right-hand return type here would fit.
|
||||
|
||||
#### Natural delegate types
|
||||
|
||||
Our main potential concerns with introducing a natural type for lambdas and method groups comes from conversion scenarios. By introducing
|
||||
a natural type and allowing it to convert to `Delegate` and making no other changes, it could potentially change resolution for `Expression<T>`
|
||||
or other complicated inference scenarios. For most cases the more specific type in the better conversion would win, but we need to verify
|
||||
that. A potential workaround is to define a specific type of conversion for delegate natural type to System.Delegate, and adjust better
|
||||
conversion to always consider this conversion to be worse. We also should consider whether delegate natural types should be natively
|
||||
convertible to `Expression<T>` as well.
|
||||
|
||||
Another important note is that by allowing single-method method groups to have a natural type, we'll be taking a step we explicitly avoided
|
||||
in the function pointer feature from C# 9. By making single-method method groups have a type, we'll be introducing a new type of breaking
|
||||
change to C#, as adding a new overload to a previously-not-overloaded will _always_ be a potential breaking change, even if no parameter
|
||||
types are ambiguous.
|
||||
|
||||
Finally, we looked at whether we should avoid synthesizing delegate types, and just restrict natural types to those that can be expressed
|
||||
as an `Action/Func`. While this would simplify implementation, it makes understanding the feature dramatically more complex, and prevents
|
||||
using either ref parameters/returns or ref struct parameters/returns.
|
||||
|
||||
### Required members
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
Finally today, we took a broad look at another revision of the required members proposal, following up on feedback from the
|
||||
[last](LDM-2021-01-11.md#required-properties-simple-form) time it was presented to the LDM. The LDT overall likes the current form of the
|
||||
proposal. There are still some open questions to resolve, but unlike previous iterations these are refinements to the proposal in its
|
||||
current form, not total rewrites. Some questions raised today and recorded in the proposal are:
|
||||
|
||||
* What is the scope of the init clause? Does it introduce a new scope like the base clause does, or does it exist at the same scope as
|
||||
the constructor? This affects whether it can use local functions defined in the constructor and whether the constructor body can shadow
|
||||
locals.
|
||||
* How many diagnostics do we want to have for silly scenarios, such as a required property that is never required from a constructor?
|
||||
* What is the severity of diagnostics we are looking to have? Namely, warnings or errors?
|
||||
* Are we using the right keywords?
|
||||
|
||||
### Appendix: ASP.NET examples
|
||||
|
||||
These are example ASP.NET programs we looked at when considering natural types, originally taken from https://github.com/halter73/HoudiniPlayground.
|
||||
|
||||
#### As written in C# 9
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var app = WebApplication.Create(args);
|
||||
|
||||
[HttpGet("/")]
|
||||
Todo GetTodo() => new(Id: 0, Name: "Play more!", IsComplete: false);
|
||||
app.MapAction((Func<Todo>)GetTodo);
|
||||
|
||||
[HttpPost("/")]
|
||||
Todo EchoTodo([FromBody] Todo todo) => todo;
|
||||
app.MapAction((Func<Todo, Todo>)EchoTodo);
|
||||
|
||||
[HttpGet("/id/{id?}")]
|
||||
IResult GetTodoFromId([FromRoute] int? id) =>
|
||||
id is null ?
|
||||
new StatusCodeResult(404) :
|
||||
new JsonResult(new Todo(Id: id.Value, Name: "From id!", IsComplete: false));
|
||||
app.MapAction((Func<int?, IResult>)GetTodoFromId);
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
record Todo(int Id, string Name, bool IsComplete);
|
||||
```
|
||||
|
||||
#### Simple proposal with natural types
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var app = WebApplication.Create(args);
|
||||
|
||||
app.MapAction([HttpGet("/")] () => new(Id: 0, Name: "Play more!", IsComplete: false));
|
||||
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) => todo);
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
record Todo(int Id, string Name, bool IsComplete);
|
||||
```
|
||||
|
||||
#### A version that uses a lambda return type
|
||||
|
||||
```cs
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
var app = WebApplication.Create(args);
|
||||
|
||||
app.MapAction([HttpPost("/")] ([FromBody] Todo todo) : Todo => todo);
|
||||
app.MapAction([HttpGet("/")] () : Todo => new(Id: 0, Name: "Play more!", IsComplete: false));
|
||||
|
||||
app.MapAction([HttpGet("/id/{id?}")] ([FromRoute] int? id) : IResult =>
|
||||
id is null ?
|
||||
new StatusCodeResult(404) :
|
||||
new JsonResult(new Todo(Id: id.Value, Name: "From id!", IsComplete: false)));
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
record Todo(int Id, string Name, bool IsComplete);
|
||||
```
|
123
meetings/2021/LDM-2021-03-10.md
Normal file
123
meetings/2021/LDM-2021-03-10.md
Normal file
|
@ -0,0 +1,123 @@
|
|||
# C# Language Design Meeting for March 10th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Property improvements](#property-improvements)
|
||||
1. [`field` keyword](#field-keyword)
|
||||
2. [Property scoped fields](#property-scoped-fields)
|
||||
2. [Parameterless struct constructors](#parameterless-struct-constructors)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I have a request for the second part of this meeting... more funny quotes!" "I'm here, don't worry." "If nothing
|
||||
else happens I'll take it I guess."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Property improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
https://github.com/dotnet/csharplang/issues/133
|
||||
|
||||
We started today by looking at these proposals again, examining the whole space to determine a priority for which to start implementing
|
||||
first. [Last time](../2020/LDM-2020-06-24.md#property-enhancements) we looked at this was 6 months ago, with mild preference for doing 133
|
||||
first, and these issues remain the issues with the largest general support on the csharplang repo.
|
||||
|
||||
Broadly speaking, C# today has 2 types of properties, each lying on the extreme end of a spectrum of flexibility:
|
||||
|
||||
* Auto-props. These have no flexibility: a backing field of the exact same type as the property is declared, and can be read from/assigned
|
||||
to only via the property getter and setter.
|
||||
* Manual props. These are maximally flexible, but have a lot of boilerplate involved and any needed backing fields are exposed to the entire
|
||||
class.
|
||||
|
||||
We also see 2 broad classes of problems that users would like to be addressed:
|
||||
|
||||
* Users would like to decorate the property. They still want a backing field of the same type as the property, but want to do some action on
|
||||
`get` or `set`. This includes things like INotifyPropertyChanged and logging. 140 has a solution for this, the `field` keyword.
|
||||
* Users would like to have either multiple pieces of state for one property, or have state that has a different type than the property itself.
|
||||
Today, these pieces of state must be exposed to the entire type, which results in naming conventions like `_value_doNotUseOutsideOfProperty`.
|
||||
133 solves this by allowing fields to be scoped to properties.
|
||||
|
||||
One important distinction we see between these problems is that the first one, solved by the `field` keyword, tends to be _much_ more pervasive
|
||||
than the second. This is true both within a single codebase (if the user is using INPC, that kills all autoprops in every view model), and in
|
||||
number of requests for the feature in general.
|
||||
|
||||
#### `field` keyword
|
||||
|
||||
The field keyword, while seemingly simple, has some interesting design decisions that we will need to settle on:
|
||||
|
||||
* What should the nullability of these backing fields be? An obvious answer is "should match the property", but this likely precludes the
|
||||
ability to do any kind of lazy init in such properties. Rather, this scenario seems similar to `var`, where we allow `null` to be assigned
|
||||
to the local, but warn when it is used unsafely. It seems possible that we could devise rules that work similarly for the `field` keyword
|
||||
here.
|
||||
* There are some concerns about how the compiler will know whether the property is a partial auto-prop or not. Just using the `field`
|
||||
keyword can lead to some complicated scenarios, particularly if the user wants to have both custom getters and setters. We could look at a
|
||||
modifier like `auto` to enable usage of the `field` keyword in the property body, or put a requirement that such properties must have either
|
||||
an auto-implemented `get` or `set`, but both of those feel like limitations we're not fans of. Investigation will be needed into how much
|
||||
complexity this will introduce for the compiler.
|
||||
* Another important concern here is that it's not just the compiler that will need to understand this: users reading will need to do the
|
||||
same determination.
|
||||
|
||||
#### Property scoped fields
|
||||
|
||||
Property scoped fields are similarly simple on first glance, but have some questions to resolve:
|
||||
|
||||
* Are these _truly_ only visible in the property? Or can they be seen from the constructor? If they're not visible in the constructor, then
|
||||
it seems like that restriction would mean `Lazy<T>` would be unusable, since it likely needs access to `this`. Related questions:
|
||||
* If they are visible, how can they be accessed? Just by name? Dotted off the property somehow?
|
||||
* Can names conflict with each other?
|
||||
* Can these property-scoped fields be seen by overrides? What are the valid accessibilities for them?
|
||||
* How do these fields influence nullable analysis?
|
||||
|
||||
We also considered whether to think of this proposal "property scoped locals", instead of as fields. However, thinking of these as locals
|
||||
raises confusing questions about lifetimes of these members, whether attributes can be applied to them, and still leaves the above questions
|
||||
unresolved.
|
||||
|
||||
Finally on this topic, we looked at whether we should expand the set of property-scoped things to methods, classes, and other definitions.
|
||||
Currently, we don't see a huge need for this, but we can revisit later if the need presents itself.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
In a switch up from the last time we looked at this, we lean heavily towards looking at the `field` keyword first. It's probably more work,
|
||||
but resolves more cases and provides more benefit to the users who want it, as it actually reduces boilerplate rather than just moving
|
||||
boilerplate.
|
||||
|
||||
We do like both of these proposals and want them both in the language, but will start by creating a detailed spec for LDM to review on 140.
|
||||
|
||||
### Parameterless struct constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/parameterless-struct-constructors.md
|
||||
|
||||
Finally today, we looked at the proposal for parameterless constructors. This proposal needs to make sure that it can handle what the general
|
||||
.NET ecosystem can do with struct constructors today, including private struct constructors.
|
||||
|
||||
Initialization behavior is one major open question. Today, constructors must initialize all fields in a struct, but they can delegate to the
|
||||
parameterless constructor to zero-initialize all fields if they choose to. If the user is defining the parameterless constructor themselves,
|
||||
they can no longer do this. We could consider making the zero-initialization an implicit part of the parameterless constructor if not all fields
|
||||
were definitely assigned, but this would create an inconsistency with how structs work in general. A related question arises with field
|
||||
initializers when no parameterless constructor is present: if one field initializer is present, do all fields have to be initialized, or can
|
||||
we infer zeroing them? We should also consider a warning for `this = default` in a parameterless constructor that has field initializers, as
|
||||
that will overwrite the field initializers.
|
||||
|
||||
We also looked at warnings around when parameterless constructors are not called, namely everywhere that `default` is used instead of `new`.
|
||||
This includes arrays, uninitialized fields, and others. One immediate comparison is to the compromises we made around nullable: we don't
|
||||
warn when an array of a non-nullable reference type is created and then immediately dereferenced, so it seems contradictory that we'd add
|
||||
warnings here. While non-defaultable structs are an interesting idea that LDM has looked at in the past, it is orthogonal to this feature
|
||||
and will need a bunch more rules than we can put in here.
|
||||
|
||||
Next, we considered scenarios where `new StructType()` does not actually mean calling the constructor, such as default parameter values. This
|
||||
is allowed today, but if we add parameterless constructors it will not actually call that constructor. Warning or erroring on such things is
|
||||
a breaking change, but the potential harm from developers thinking a constructor is being called when it's not is likely greater than any
|
||||
harm to users that would need to specify `default` instead of `new`. We should consider erroring when a parameterless constructor exists for
|
||||
these cases, and having a warning wave to cover the cases when there isn't a parameterless constructor to help guide users to idiomatic patterns.
|
||||
|
||||
Finally, we looked at interactions with generic type arguments. The `new()` constraint required a _public_ constructor, internal and private
|
||||
do not work for this. We can block direct substitution for such type parameters, but indirect substitution would be possible as `where T : struct`
|
||||
already implies `new()` today. We don't think that we can realistically block all `where T : struct` substitutions for structs with non-public
|
||||
constructors, and we think that a warning for these cases is likely to produce far too many false positives to be useful. This may be a case
|
||||
where we have to simply hold our noses and compromise on letting the runtime exception happen.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
Overall, the spec is in good shape, and we'll get started on implementation. As it comes up in the implementation work, we'll work through
|
||||
open questions.
|
76
meetings/2021/LDM-2021-03-15.md
Normal file
76
meetings/2021/LDM-2021-03-15.md
Normal file
|
@ -0,0 +1,76 @@
|
|||
# C# Language Design Meeting for March 15th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Interpolated string improvements](#interpolated-string-improvements)
|
||||
2. [Global usings](#global-usings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Need is a strong word... I'm just teasing, I'm just lobbing in a joke"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Interpolated string improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
We looked at the open question around argument passing today. In the current form, the proposal treats the receiver specially, even
|
||||
for reduced extension methods where the receiver is just sugar for the first argument. This special treatment of the receiver is
|
||||
very limiting, as many API patterns exist today where destinations are conveyed as arguments to a method, not as the receiver of the
|
||||
method, and we would like APIs to be able to continue having their same shapes. It's also inconsistent with how we treat extension
|
||||
methods in the rest of the language: they're a sugar, and there is no semantic difference between calling them in reduced form or not.
|
||||
|
||||
To address this, we looked at a couple of possible solutions:
|
||||
|
||||
1. Don't pass anything. No receiver, no arguments. This is clean, but is not really an improvement on how it works today. It means that
|
||||
the builder type would need to support storage of an arbitrary number of arguments of arbitrary types, and would preclude working with
|
||||
ref structs and other types that can't be in generics today.
|
||||
2. Require methods to have a `PrepFormat` or similar signature that is isomorphic to the original signature, with the builder parameters
|
||||
as `out` variables instead. This would allow us to simply call that signature, and there would be no fancy mapping to consider. However,
|
||||
this is pretty messy from a public API standpoint. We can apply `EditorDisplay` to such methods, but they'll still exist, need to be
|
||||
documented, and generally get in the way. It also means that every method will need to have a second method, which possibly limits code
|
||||
sharing abilities.
|
||||
3. Put an attribute on either individual parameters or on the builder type, specifying that these parameters should be passed to the
|
||||
builder creation method. The former version was proposed in the open questions section, but the latter came up in discussion and seems
|
||||
like a better approach. It gives a central location, handles multiple potential builders in a method signature, and allows including the
|
||||
receiver type as either a magic name like `this` or as a boolean flag.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll look at the attribute on the builder type solution. The next question we need to answer is around out-of-order execution for
|
||||
arguments passed to builders.
|
||||
|
||||
### Global usings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
Next, we looked at 2 open issues in global usings: the scope of the global using statements, and how name lookup treats these usings
|
||||
with respect to regular using directives. Both of these questions really hinge on the overall view we want to take on how global usings
|
||||
are represented. There are 2 views here:
|
||||
|
||||
1. Global usings are effectively copy/pasted into the top of every file. They're just like regular `using` directives, and all the same
|
||||
rules that apply to regular `using` directives at the top level apply to global usings as well.
|
||||
* This has the advantage that, at the top level, a regular `using` directive will _never_ be changed by a global using directive.
|
||||
Users can't introduce namespace ambiguities at the top level by introducing a global using directive that has a nested namespace with
|
||||
the same name as a top-level namespace.
|
||||
* It aligns with our prior art: both CSX and VB.NET work this way.
|
||||
* This version has the disadvantage that, at the top level, global aliases could not be reused in a file-scoped using alias. They could
|
||||
be used in an alias nested inside a namespace declaration, but not at the top level. While this is a niche scenario, it is likely that
|
||||
someone will hit it at some point.
|
||||
* If a name is imported in both a global `using` directive and a file-scoped `using` directive, that name is ambiguous.
|
||||
2. Global usings are a new scope, that exists _above_ regular `using` directives at the top of a file. They are to file-scoped `using`
|
||||
directives what file-scoped `using` directives are to `using` directives nested in a namespace.
|
||||
* This would allow globally-defined aliases to be used in file-scoped directives.
|
||||
* We would need to rationalize how these rules work for CSX. Do global using directives imported from a `#load` directive change the
|
||||
meaning of `using` directives in the current file, or files that are subsequently `#load`ed?
|
||||
* Name lookup would prefer the file-scoped `using` directives, only going to global directives if a name wasn't found.
|
||||
* Has a bigger implementation cost: some PDB changes might be required in order to ensure that names mean the correct things and diverges
|
||||
heavily from existing implementations.
|
||||
|
||||
We think both worldviews here are consistent and reasonable, and would be fine with either mental model as an explanation to consumers of how
|
||||
the feature works. However, worldview 1 is more consistent with past decisions and has less implementation concerns.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with worldview 1. The decisions for scoping and name lookup fall out from this.
|
70
meetings/2021/LDM-2021-03-24.md
Normal file
70
meetings/2021/LDM-2021-03-24.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
# C# Language Design Meeting for March 24th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
2. [`field` keyword](#field-keyword)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "We all want to be happy"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Today, we looked at one of the open questions in improved interpolated strings, conditional evaluation of the interpolated
|
||||
string holes. The proposal today allows the interpolated string builder to return `false` from creation or from any `TryFormat`
|
||||
methods, which short-circuits evaluation of any of the following interpolation holes. This has some pros and cons:
|
||||
|
||||
* Pro: Interpolation holes can be expensive, and if they don't need to be executed it gives an easy way to achieve semantics
|
||||
that people often do with lambdas currently.
|
||||
* Con: This is "invisible" to the user. There isn't a way, looking at an interpolated string expression as an argument to a
|
||||
method, to tell whether the holes in this string will be conditionally-evaluated or not.
|
||||
|
||||
This conditional evaluation can have visible impacts when the interpolated string expression has side-effects. For example,
|
||||
a conditionally-evaluated string won't have its holes impact definite assignment, because we can't be sure that the expressions
|
||||
were evaluated. Further, due to the way the proposal is written, upgrading to a new version of a library could change the
|
||||
semantics of existing code, as the library author could introduce a new overload that the interpolated string prefers, introducing
|
||||
conditional evaluation of what used to be unconditionally-evaluated code. While library upgrades can always introduce behavior
|
||||
changes in a user's code (introducing a new instance method that is preferred over an extension method, for example), this would
|
||||
be expanding such concerns.
|
||||
|
||||
We considered whether to have some syntax marker to indicate that "the interpolated string expression holes will be conditionally
|
||||
evaluated", such as putting a `?` in the hole (like `$"{?name}"`). While this syntax calls out that conditional evaluation is
|
||||
occurring, we're concerned that it leans too far into making new language features obvious. We'd need to start shipping analyzers
|
||||
that ensure that users are using this syntax where possible, and it could become another pitfall of "make sure you add this syntax
|
||||
for maximum performance."
|
||||
|
||||
Finally, we looked at whether we could investigate a broader feature around lazily-evaluated arguments. Today, users often use
|
||||
lambdas to defer this type computation. We're concerned by building a feature like this on lambdas, however, because of the
|
||||
implicit allocation cost here. If we make advances in this space later, we can look at incorporating those advances at that point,
|
||||
but we feel that we can move forward with conditional evaluation at this point.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will have conditional evaluation of interpolated string holes without a special syntax for calling this out.
|
||||
|
||||
### `field` keyword
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
We looked at how the `field` identifier will be resolved inside a property, and what exactly will indicate to the compiler that the
|
||||
property should have a backing field generated for it. The proposal calls for resolving `field` to the backing field when there is
|
||||
no other identifier named that in scope. We thought about a few alternatives:
|
||||
|
||||
* Could we make `field` _always_ a contextual keyword in property bodies, gated by target language version? We made a bigger break
|
||||
with `record` type names, but it was a very different break. `record` was used as a type name, which are by-convention PascalCase
|
||||
in C# programs. Here, we'd be breaking `field` as an identifier, which is much more common and fits in with standard C# naming
|
||||
practices.
|
||||
* Could we use a `__` name? Identifiers with a `__` are reserved by C#, so we can use one without breaking anyone. However, `__`
|
||||
names are not common, only used for things that aren't common practice in C# (such as `__makeref` or `__arglist`).
|
||||
* Could we take a page from VB's book, and introduce a `_PropName` identifier? While it still has a potential conflict with class
|
||||
fields, the conflict should be much smaller, and theoretically resolving the ambiguity could be as easy as deleting the class field
|
||||
(provided we get the naming right), whereas a class `field` identifier could be entirely unrelated to the current property.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'd like to explore the last 2 proposals a bit, and come back to the LDM with a fleshed out proposal after some thinking about it.
|
100
meetings/2021/LDM-2021-03-29.md
Normal file
100
meetings/2021/LDM-2021-03-29.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
# C# Language Design Meeting for March 29th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Parameterless struct constructors](#parameterless-struct-constructors)
|
||||
2. [AsyncMethodBuilder](#asyncmethodbuilder)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I told people to question my vague memory and now I'm upset that you did"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Parameterless struct constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md
|
||||
|
||||
Today, we took a look at open questions in this proposal.
|
||||
|
||||
#### Field initializers and implicit constructors
|
||||
|
||||
The main question on our mind here is, what does this code print?
|
||||
|
||||
```cs
|
||||
// Is this `default(S)`, or does `Field` have a value of 42?
|
||||
var s = new S();
|
||||
System.Console.WriteLine(s.Field);
|
||||
|
||||
public struct S {
|
||||
public int Field = 42;
|
||||
|
||||
public S(int i)
|
||||
{
|
||||
// C# executes:
|
||||
// Field = 42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are a few possibilities for what this can imply:
|
||||
|
||||
1. There is an implicit parameterless constructor that runs the field initializer. The code sample would print 42.
|
||||
2. No implicit parameterless constructors are synthesized. We warn on the field initializer if an explicit parameterless constructor
|
||||
is not provided. The code sample would print 0.
|
||||
3. An implicit parameterless constructor is synthesized _when no other explicit constructors are provided_. The code sample would
|
||||
print 0.
|
||||
|
||||
The complication here comes from the fact that `new S()` was and will continue to be legal, even when there is no parameterless
|
||||
constructor. This differs from class types, which do not have an implicit parameterless constructor that would run such a field
|
||||
initializer. Therefore, one way that we could look at the problem is under the lens of "We've always had this constructor, it's
|
||||
just been omitted as an optimization". By that rule, option 1 should be the tack we take. However, we are also concerned that some
|
||||
users will have taken a dependency on that parameterless constructor meaning a 0-initialized struct.
|
||||
|
||||
Another complication was raised in conjunction with record struct primary constructors. In record classes today, we require that if
|
||||
the record has a primary constructor, all other constructors must chain to it. This would mean that the parameterless constructor
|
||||
needs to chain to the primary constructor, which wouldn't work for an implicit parameterless constructor. We could require that record
|
||||
structs with field initializers and a primary constructor provide a parameterless constructor that chains, but that also brings up
|
||||
concerns about nullable annotations of parameters. In C# today, we generally advise struct authors to annotate for typical use, not
|
||||
for all the technical possibilities. That means that if users shouldn't be using a default struct, we advise that a reference field
|
||||
could be annotated not null. That would interact poorly with a requirement to define the primary constructor here, as there could
|
||||
very well be _no_ valid default to specify.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
The points raised today around interactions with primary constructors need more thought. We'll take this back for more workshopping,
|
||||
and bring it to LDM again later.
|
||||
|
||||
#### Other conclusions
|
||||
|
||||
We also came to a couple other conclusions this session, affirming the proposed behavior of:
|
||||
|
||||
* Definite assignment simplification: the proposed rules are accepted. Further general relaxation of definite assignment might be
|
||||
interesting, but require a separate proposal.
|
||||
* Some of the language around how the C# compiler handles private and internal constructors today needs to be modified a bit to
|
||||
reflect more nuances, but we approve of the general goal: keep behavior unchanged.
|
||||
|
||||
### AsyncMethodBuilder
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md
|
||||
|
||||
We started today by questioning whether we can simplify this proposal a bit. Today, the proposal covers both a scoped version and
|
||||
a single-method version. The scoped version is complicated, and has a number of downsides:
|
||||
|
||||
* Diagnostic story is unclear. If the user thought they were applying the builder to all methods with a specific return type but
|
||||
did it incorrectly for a subset of the locations, we have no good way to let them know they made an error.
|
||||
* Relatedly, a scoped version would need a good amount of IDE work to help users understand what builder type this method was actually
|
||||
using.
|
||||
|
||||
When we look at our use cases for this attribute, we generally believe that there are a couple of main ones. First, and the use case
|
||||
the runtime is interested in, is pooling. This usecase is unlikely to want module-wide application because pooling is potentially
|
||||
harmful for infrequently-used APIs, and so users would want to be more precise about which specific methods in a type are high
|
||||
usage and which are not. The other big use case we can see is builders that responsible for tracing. These builders could potentially
|
||||
be wanted module-wide so that tracing could be added everywhere, but we are again worried about the diagnostic and user-info story
|
||||
for this.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will remove the scoped version of this proposal for now, and use the existing `AsyncMethodBuilder` attribute on an individual method,
|
||||
local function, or lambda, to control the builder type for that function. If we hear demand for a module-scoped version in the future,
|
||||
we can use a new attribute designed for that case and think about the diagnostic situation at that point.
|
151
meetings/2021/LDM-2021-04-05.md
Normal file
151
meetings/2021/LDM-2021-04-05.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
# C# Language Design Meeting for April 5th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Interpolated string improvements](#interpolated-string-improvements)
|
||||
2. [Abstract statics in interfaces](#abstract-statics-in-interfaces)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Every time someone tells me they have a printer that works, I believe they're part of the conspiracy"...
|
||||
"So what I'm getting from this is that you've given up on your printer and are now printing at the office?"
|
||||
|
||||
NB: This is, to my knowledge, the largest amount of time between parts 1 and 2 of an LDM quote (approximately 1 hour and 10 minutes,
|
||||
in this instance).
|
||||
|
||||
## Discussion
|
||||
|
||||
### Interpolated string improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Our focus today was in getting enough of the open questions in this proposal completed for the [dotnet/runtime API proposal](https://github.com/dotnet/runtime/issues/50601)
|
||||
to move forward.
|
||||
|
||||
#### Create method shape
|
||||
|
||||
We adjusted the shape of the `Create` method from the initial proposal to facilitate some improvements:
|
||||
|
||||
* In some cases, `out`ing a parameter with a reference type field can cause the GC to introduce a write barrier.
|
||||
* For the non-`bool` case, `Create` feels more natural.
|
||||
|
||||
This may be a micro-optimization, but given that this pattern is not intended to be user-written, but instead lowered to by the compiler,
|
||||
we feel that it's worth it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Approved.
|
||||
|
||||
#### TryFormatX method shape
|
||||
|
||||
The proposal suggests that we should allow `TryFormatX` calls to either return `bool` or `void`, as some builders will want to stop formatting
|
||||
after a certain point if they run out of space, or if they know that their output will not be used. While a `void`-returning method named `TryX`
|
||||
isn't necessarily in-keeping with C# style, we will keep the single name for a few reasons:
|
||||
|
||||
* Again, this pattern isn't intended to be directly consumed by the end user, they'll be writing interpolated strings that lower to this.
|
||||
* It's harder for API authors to mess up and mix `void`-returning and `bool`-returning `TryFormatX` methods, because you can't overload on
|
||||
return type.
|
||||
* It's easier to implement.
|
||||
|
||||
We do want to allow mixing of a `Create` method that has an `out bool` parameter, as some builder are never going to fail after the first create
|
||||
method. A logger, for example, would return `false` from `Create` if the log level wasn't enabled, but the actual format calls will always succeed.
|
||||
|
||||
We also looked at ensuring that a single `TryFormatInterpolationHole` method with optional parameters for both alignment and format components can
|
||||
be used with this pattern. As specified today, these parameters are not named so overload resolution can only look for signatures by parameter type.
|
||||
This means there's no single signature that handle just a format or just an alignment. To resolve this, we will specify the parameter names we use
|
||||
for the alignment and format components (their names will be `alignment` and `format`, respectively). The first parameter will still be unnamed.
|
||||
|
||||
Another discussion point was on whether we should require builders to _always_ support all possible combinations of a format or alignment component
|
||||
being present. For some types (such as `ReadOnlySpan<T>`), we're imagining that such components would be just ignored by the builder, and it would
|
||||
be interesting to just have that be a compile error instead of just being silently ignored at runtime. However, the ship on invalid format specifiers
|
||||
sailed a long time ago, and there is potential difficulty in making a good compile-time error experience for these scenarios. Thus, our recommendation
|
||||
is to always include overloads that support these specifiers, even when ignored at runtime.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
API shape is approved. We will use named parameters for `alignment` and `format` parameters, as appropriate.
|
||||
|
||||
#### Disposal
|
||||
|
||||
Finally in interpolated string improvements, we looked at supporting disposal of builder types. Our decision that we will have conditional execution
|
||||
of interpolated string holes means that user code will be running the middle of builder code. This means that an exception can be thrown, and if the
|
||||
builder acquired resources (such as renting from the `ArrayPool`), it has the potential to be lost if we do not wrap the builder (and potentially
|
||||
larger method call that consumes the builder for non-`string` arguments) in a try/finally that calls dispose on the builder. There's also an additional
|
||||
complication to the disposal scenario, which is whether we trust the compile-time type of the builder. If the builder is a non-sealed reference type
|
||||
or a type parameter constrained to an interface type that has an `abstract static Create` method, the actual runtime type could implement `IDisposable`
|
||||
that we do not know about at runtime. In the language, we're not hugely consistent on this. In some cases we trust that the compile-time type is correct
|
||||
(such as when determining if a sealed/struct type is disposable), and in some cases we emit a runtime check for `IDisposable` (such as if the enumerator
|
||||
type is a non-sealed type or interface). We also need to consider whether to do pattern-based `Dispose` for all types (as we do in `await foreach`) or
|
||||
just for `ref struct`s (as we do for regular `foreach`).
|
||||
|
||||
Even the concept of disposal for these builder types might not be needed. The runtime is a bit leary of having `InterpolatedStringBuilder` be disposable,
|
||||
because it means that every interpolated string will introduce a `try/finally` in a method. We really don't want to see people create performance analyzers
|
||||
that tell users to avoid interpolated strings in methods because it will affect inlining decisions. We have some ideas on avoiding this for interpolated
|
||||
strings converted to strings specifically, but a general pattern is concerning. If the main builder type won't be disposable, are we concerned that we're
|
||||
trying to engineer a solution to a problem that doesn't actually exist?
|
||||
|
||||
##### Conclusion
|
||||
|
||||
No conclusion on this point today. A small group will examine this in more detail and make recommendations to the LDM based on evaluation.
|
||||
|
||||
### Abstract statics in interfaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4436
|
||||
|
||||
We looked at 2 major changes to the proposal today: allowing default implementations and changes to operator restrictions.
|
||||
|
||||
#### Default implementations
|
||||
|
||||
The existing proposal specifically scopes default implementations of virtual statics in interfaces out because of concerns about runtime implementation
|
||||
costs. In particular, in order to make default implementations work and be able to call other virtual static members, there must be a hidden "self" type
|
||||
parameter so that the runtime knows what type to call the virtual static method on. That work is still too complicated to bring into C# 10, but a simple
|
||||
change of scope can make a subset of these cases work: we could require that all static virtual members _must_ be called on a type parameter. This takes
|
||||
that hidden "self" parameter and makes it no longer hidden, because there must always be a thing that the user calls that actually contains the type. It's
|
||||
not always necessary to write `T` itself: for example, `t1 + t2` would reference the static virtual `+` operator on `T`, so it's being accessed on the
|
||||
type parameter, not on the interface.
|
||||
|
||||
However, there are some serious usability concerns for this approach. A default implementation of a virtual static member cannot be inherited by any
|
||||
concrete types that inherit from that interface. This is true for instance methods as well, but that method (or a more derived type's implementation of
|
||||
the method) can be accessed by casting that instance to the interface type in question. For a DIM of a virtual static member, there is no instance that
|
||||
can be cast to the base interface to access the member, so a concrete type must always reimplement the virtual static member or it will be truly inaccessible.
|
||||
For example:
|
||||
|
||||
```cs
|
||||
interface I<T> where T : I<T>
|
||||
{
|
||||
public static abstract bool operator ==(T t1, T t2);
|
||||
public static virtual bool operator !=(T t1, T t2) => !(t1 == t2);
|
||||
public void M() {}
|
||||
}
|
||||
class C : I<C>
|
||||
{
|
||||
public static bool operator ==(C c1, C c2) => ...;
|
||||
}
|
||||
|
||||
C c = new C();
|
||||
c.M(); // M is not accessible
|
||||
((I)c).M(); // This works, however
|
||||
|
||||
_ = c == c; // Fine: C implements ==
|
||||
_ = c != c; // Not fine: I.!= is a default implementation that C does not inherit. This method cannot be called.
|
||||
```
|
||||
|
||||
There are 2 workarounds a user could use for this problem: make a generic method that takes a type parameter constrained to I and invoking the member there,
|
||||
or reimplementing the operator on C, thereby removing the benefit of the default implementation in the first place. Neither of these are particularly appealing.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
A smaller group will revisit this and decide whether it is useful. We are skeptical of it based on the above problems currently.
|
||||
|
||||
#### Operator restrictions
|
||||
|
||||
Today, interfaces are prohibited from implementing `==` or `!=`, and from appearing in user-defined conversion operators. We have a long history of this, mainly
|
||||
because with interfaces there is always the change that the underlying type actually implements the interface at runtime, and the language should always prefer
|
||||
built-in conversions to user-defined conversions. However, these operators cannot actually be accessed when the user only has an instance of the interface, they
|
||||
can only be accessed when using a concrete derived type or a type parameter constrained to the interface type. This addresses our concerns about interface
|
||||
implementation at runtime, and conversions from types defined in interfaces is particularly useful for numeric contexts such as being able to allow `T t = 1;`
|
||||
because `T` is constrained to an interface that has a user-defined conversion from integers.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Lifting these restrictions is approved.
|
95
meetings/2021/LDM-2021-04-07.md
Normal file
95
meetings/2021/LDM-2021-04-07.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# C# Language Design Meeting for April 7th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
MVP-nominated session agenda
|
||||
|
||||
1. [Abstract statics methods](#abstract-statics-methods)
|
||||
2. [Making other language literals easier](#making-other-language-literals-easier)
|
||||
3. [Expression trees](#expression-trees)
|
||||
4. [Metaprogramming](#metaprogramming)
|
||||
5. [Cost-free delegates and LINQ](#cost-free-delegates-and-linq)
|
||||
6. [Readonly locals and parameters](#readonly-locals-and-parameters)
|
||||
7. [Global usings](#global-usings)
|
||||
8. [Mixed-language projects](#mixed-language-projects)
|
||||
9. [Discriminated unions](#discriminated-unions)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Cats are the superior pet" - followed by a lot of discussion that isn't relevant, as your note taker is a cat person
|
||||
|
||||
## Discussion
|
||||
|
||||
Today we held a more informal LDM session with Microsoft MVPs, where we selected an agenda from their suggestions and a brief discussion
|
||||
on our thoughts around the topic, as well as getting their impressions and potential concerns on the topic.
|
||||
|
||||
### Abstract statics method
|
||||
|
||||
One thing we heard today was that there are definite use cases for this feature, not just in interfaces, but also in class types. This will
|
||||
particularly help with abstracting construction scenarios. There are also likely cases in Objective-C and Swift interop code that will be
|
||||
made simpler with this feature, which we hadn't yet looked at as a possible application for this. We also briefly discussed the issue of
|
||||
multiple inheritance with default implementations of abstract methods, but no new problems arise from abstract statics that did not already
|
||||
exist with default implementations of instance interface members.
|
||||
|
||||
### Making other language literals easier
|
||||
|
||||
A common complaint in C# is when users need to include literals from other languages such as XML or JSON, or even C# itself (particularly
|
||||
evident in source generators). While we aren't looking for language-specific literal types, we are looking at a more general "raw string"
|
||||
proposal that will allow more customization of escapes inside the string, which will address the problems around copying and pasting text
|
||||
that includes quote marks in it.
|
||||
|
||||
### Expression trees
|
||||
|
||||
Some questions were raised on why we haven't updated expression trees since initial release. Our concerns here are around expression tree
|
||||
consumers: any update we make to start including new things in the tree will likely break consumers of expression trees. This starts to get
|
||||
particularly gnarly when we consider libraries that users want to ship and support on older target frameworks, or when newer code wants to
|
||||
depend on older code that hasn't been updated for handling of new expression tree constructs. We need a good proposal around how to handle the
|
||||
guardrails in these tricky scenarios to move this forward, and we don't have one yet.
|
||||
|
||||
### Metaprogramming
|
||||
|
||||
Questions here centered around 2 points. First was ease of use, such as being able to define a source generator in the same project that
|
||||
consumes it. We are currently focussed on the V2 of the source generator API, but after that is shipped we'll be turning to focus on more
|
||||
general ergonomics, and this will be one of the areas we'll investigate. There are some particularly hard questions around running a source
|
||||
generator when the project doesn't compile.
|
||||
|
||||
The second point is in source replacement. We are very unsure about this feature: in particular, the IDE experience isn't great. We've
|
||||
already received occasional bugs from users about IDE experiences with existing code-rewriting solutions such as Fody (such as debuggers
|
||||
appearing to misbehave), and we have additional concerns about the perf implications of such generators.
|
||||
|
||||
### Cost-free delegates and LINQ
|
||||
|
||||
A number of scenarios would like lambdas to be more efficient. In particular, some applications of LINQ should be able to be cost-free, as
|
||||
the results are immediately used and don't need to allocate closures. However, we don't have the language facilities to do this today. If
|
||||
we add the ability for ref structs to implement interfaces and be used in generics, we could get this feature, but usage would be extremely
|
||||
unergonomic due to the inability to infer generics parameters for some of the cases involved. To really make a feature like this usable, we
|
||||
would also want associated/existential types to remove those uninferrable generic parameters.
|
||||
|
||||
### Readonly locals and parameters
|
||||
|
||||
This has been one of the ultimate examples of bikeshedding. We have an obvious syntax in `readonly`, but we are concerned that the effort
|
||||
the user would put into putting `readonly` everywhere doesn't match the actual benefit they would derive from doing so. C#'s past a
|
||||
mutable-first language is definitely a liability with this issue.
|
||||
|
||||
### Global usings
|
||||
|
||||
We revisited why we are looking at this feature as C# syntax, and not as a project file switch. There are 2 reasons for this: we think we
|
||||
can do a better tooling experience in source, and we think that it will compose well with using aliases to enable other much-requested
|
||||
features. It does make multiple-project aliases a bit more difficult, as the user will have to include the file from multiple projects,
|
||||
but we think it's a worthwhile tradeoff. We also looked at global usings nested inside a namespace. We're concerned about the user experience
|
||||
for such constructs, and that the use case isn't there currently. If we see a need for this in the future, we can look at it then.
|
||||
|
||||
### Mixed-language projects
|
||||
|
||||
While this is an interesting idea, the compiler support here is somewhat scary. Additionally, VB.NET and C# have a number of very subtle
|
||||
differences that could very easily lead to developer confusion. Overall, we're not pursuing this area.
|
||||
|
||||
### Discriminated unions
|
||||
|
||||
While we wanted to look at discriminated unions for C# 10, we don't think we're going to be able to. In particular, there are some big questions
|
||||
left around type inference to be solved. `Option<T>.None` won't be particularly ergonomic until we do so. We also took a brief survey to see
|
||||
what the MVPs are looking to get out of the feature:
|
||||
|
||||
* Exhaustiveness in pattern matching
|
||||
* Brevity in declaration
|
||||
* Either-or parameters to methods
|
102
meetings/2021/LDM-2021-04-12.md
Normal file
102
meetings/2021/LDM-2021-04-12.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
# C# Language Design Meeting for April 12th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [List patterns](#list-patterns)
|
||||
2. [Lambda improvements](#lambda-improvements)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "If the runtime team jumped off a bridge, would you [redacted]?" "If it would make my code faster"
|
||||
- "Quote of the day may need to involve corn fields"
|
||||
|
||||
## Discussion
|
||||
|
||||
### List patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3435
|
||||
|
||||
Today we looked over the changes around supporting `IEnumerable` types in list patterns, and how slice patterns will affect them. While
|
||||
we overall like the lowering that has been proposed, we would like to continue to look at possible optimizations around using framework
|
||||
apis such as `TryGetNonEnumeratedCount`. This will allow the framework to do all the grungy bookkeeping to make this quick for cases when
|
||||
the `IEnumerable` is actually a countable type, or one of a certain type of LINQ expression that is countable under the hood (such as a
|
||||
`Select` over an array). The runtime is also planning on adding a `Take` method that does similar bookkeeping with index and range, falling
|
||||
back to buffering if it has to.
|
||||
|
||||
We also considered to what extent we should allow sub-patterns under a slice pattern. The obvious use case is a declaration pattern for the
|
||||
slice, but as worded today the syntax will allow any arbitrary sub-pattern on the slice. After discussion, while we think that the main use
|
||||
case is just declaration patterns, we see no reason to disallow other nested sub-patterns. It should compose well, and while 99% of users
|
||||
will never need it, it could be helpful for that 1%.
|
||||
|
||||
The other question with slice patterns is whether to allow nested sub-patterns for `IEnumerable` as well as for sliceable types. If we do
|
||||
allow this, it would mean treating `IEnumerable` specially here, while indexers and slicers cannot be used in the general case. Given
|
||||
this, we think it will be a better experience if we only allow sub-patterns under a slice if the type is actually sliceable. Separately, we
|
||||
can look at considering extension methods as a part of general sliceability, which, if combined with renaming that `Take` method from the
|
||||
framework to `Slice` before it ships, would enable the feature in a much more general fashion.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
* Lowering is generally approved.
|
||||
* Slice sub-patterns will allow any pattern, so long as the input type to the list pattern is sliceable.
|
||||
* Making `IEnumerable` sliceable is orthogonal.
|
||||
|
||||
### Lambda improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
|
||||
|
||||
After some implementation work, this proposal is back to look at some more changes. One of the big ones is moving the return type to the left
|
||||
side of the parameter list. This makes the syntax analogous to our other signature declarations in C#, looking like an unnamed version of a
|
||||
local function. This fits in well with the types of changes we've been making to C# in recent versions, such as tuples/record structs, anonymous
|
||||
classes/record classes, and declaration forms/pattern forms. While we do have `Func` and `delegate*` as types that put the return last, we are
|
||||
at least consistent within type forms and within signature declaration forms. At this point, the only parity we are missing between lambdas
|
||||
and local functions is the ability to declare generic type parameters, but those won't be useful on lambda expressions unless we add
|
||||
higher-kinded types to the language.
|
||||
|
||||
We also looked at requirements around return type specification: do we want to require that, if a return type is specified, parameter types must
|
||||
be specified too? On the one hand, we require that, if any parameter types are specified, the others must be specified as well. On the other,
|
||||
we can't make it required to specify the return type when a parameter type is specified because C# hasn't had such an ability until now, so
|
||||
requiring this would lack symmetry. It's also very possible that the return type of a lambda could be uninferrable, while the parameter types
|
||||
are, and it would be unfortunate if you had to specify everything in order to just get the return type. Given this, we will allow the return
|
||||
type to be specified without parameter types.
|
||||
|
||||
We considered the case of `async async (async async) => async`. We could make it required to escape `async` used as a type name here: if the user
|
||||
wants to specify the return type of the lambda is the type named `async` and the lambda is _not_ an async lambda, they'll have to escape it anyway
|
||||
to disambiguate between making the lambda `async`. Given this, we support making it required to escape the type name in the return type.
|
||||
|
||||
The changes around attributes in the proposal raised no concerns. These changes were requiring that, if an attribute is used, parentheses must
|
||||
be used for the parameter list, and the rules around how the compiler will disambiguate conditional array accesses and dictionary initializers.
|
||||
|
||||
Finally, we noted the possibility for a breaking change if single-method method groups are given a natural type with the following code:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
|
||||
var c = new C();
|
||||
D.M(c.MyMethod);
|
||||
|
||||
// Outputs "Extension" today, would print Instance if c.MyMethod had a natural type.
|
||||
|
||||
class C
|
||||
{
|
||||
public void MyMethod() { Console.WriteLine("Instance"); }
|
||||
}
|
||||
|
||||
static class CExt
|
||||
{
|
||||
public static void MyMethod(this C c, string s) { Console.WriteLine("Extension"); }
|
||||
}
|
||||
|
||||
class D
|
||||
{
|
||||
public static void M(Delegate d) { d.DynamicInvoke(); }
|
||||
public static void M(Action<string> a) { a(""); }
|
||||
}
|
||||
```
|
||||
|
||||
We'll need to investigate to see if we can avoid this change, or if we're ok with the potential break.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
* Return type before the parameter list is approved, does not require parameter types to be specified.
|
||||
* `async` as a return type name should require escapes.
|
||||
* Attribute changes are approved.
|
90
meetings/2021/LDM-2021-04-14.md
Normal file
90
meetings/2021/LDM-2021-04-14.md
Normal file
|
@ -0,0 +1,90 @@
|
|||
# C# Language Design Meeting for April 14th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Shadowing in record types](#shadowing-in-record-types)
|
||||
2. [`field` keyword](#field-keyword)
|
||||
3. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "We're taking my sarcastic suggestion"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Shadowing in record types
|
||||
|
||||
In C# 10, we want to allow the user to change whether a record primary constructor parameter is a property or a field. However, we run into an
|
||||
issue with currently-legal C# 9 code like this:
|
||||
|
||||
```cs
|
||||
using System;
|
||||
|
||||
var c = new C(2);
|
||||
c.Deconstruct(out var x);
|
||||
Console.WriteLine(x); // Prints 1
|
||||
|
||||
public record Base
|
||||
{
|
||||
public int X { get; set; } = 1;
|
||||
}
|
||||
public record C(int X) : Base
|
||||
{
|
||||
public new int X = X;
|
||||
}
|
||||
```
|
||||
|
||||
In this example, what will deconstruct refer to? In C# 9, it will be `Base.X`, but if we allow changing whether `X` is a property or not then
|
||||
C# 10 will consider that to be `C.X`, and the code will print 2. We consider this to a pathological case and not something we intended to enable
|
||||
in C# 9, so we will take a hard-line approach and patch C# 9 to make shadowing a base record property like this an error.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Make this code an error in C# 9. In C# 10, `Deconstruct` will refer to `C.X`.
|
||||
|
||||
### `field` keyword
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
After our [last](LDM-2021-03-24.md#field-keyword) meeting on partially-implemented auto-properties, we had decided to look more deeply at a
|
||||
few alternate syntaxes to see if we could avoid the need to make `field` a contextual keyword. While some of the suggestions would take less
|
||||
compiler work, our ultimate conclusion here is that we'd prefer to do the work needed to make `field` a contextual keyword over taking what we
|
||||
feel is an inferior syntax. As part of this work, we can consider making the public Roslyn APIs around backing fields more consistent. Today,
|
||||
there are differences between what `GetMembers()` will return for auto-properties vs field-like events, and in order to implement the `field`
|
||||
keyword we will likely need to take the same strategy as we did with field-like events to avoid circularities. We will also consider standardizing
|
||||
the behavior here as it has also been a source of Roslyn-API consumer confusion in the past. We will also consider a .NET 6 warning wave to prefix
|
||||
`field` identifiers in properties that do not correspond to a backing field with either `@` or `this.`, as appropriate.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
We will proceed with `field` as the keyword, and would like to have .NET 6 warning wave to nudge users to write code that has no chance of being
|
||||
ambiguous.
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Today, we took a look at two questions. First, should we allow interpolated string builders to be passed by ref? And if so, what, if any, is
|
||||
syntax needed on the builder? We think this will be important for both safety and optimal performance in some framework builders: if the builder
|
||||
is a struct type and is passed by reference and it captures any reference types as fields, then those fields can be cleared by the called method,
|
||||
which will help ensure things aren't unnecessarily rooted. We already have a good precedent for this in extension methods: struct types are allowed
|
||||
to be passed by `ref` as the `this` parameter, and no `ref` is required at the call site. We think the same rules will work here as well.
|
||||
|
||||
We also took a look at out-of-order parameter evaluation. Our current proposal is that a builder can be annotated with an
|
||||
`InterpolatedStringBuilderArgument` attribute, and that the compiler will look at the names in the attribute to determine which parameters to pass
|
||||
to the `Create` method call of the builder. However, we have a question of what to do if the named parameter is _after_ the interpolated string
|
||||
literal and the interpolated string is being lowered using the `bool`-returning `Append` methods, as we will have to evaluate the trailing parameter
|
||||
before the contents of the interpolated string. This would break lexical ordering, which is likely to be extremely confusing and have detrimental
|
||||
effects on definite assignment, as well as user understanding. However, in order for maximum compatibility with existing API signatures, this would
|
||||
have to be supported, as there existing API shapes (such as `Utf8Formatter`) that put the location the interpolated string would go before some of
|
||||
the arguments that the builder will need (in the case of `Utf8Formatter`, their signatures are the value to be formatted, then the destination span).
|
||||
We could potentially make this dependent on the `Append` calls used the by the builder: `void`-returning `Append`s would allow arguments after the
|
||||
builder, and `bool`-returning `Append`s would not. This would allow the compiler to evaluate the interpolation holes ahead of time and preserve
|
||||
lexical ordering. However, it has its own downsides of adding confusion to the builder pattern, and might be more difficult to tool. Further discussion
|
||||
on this topic revealed that we need to revisit the whole question of conditional execution in order to make a decision here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
* Ref parameters are approved, using the same rules as extension method `this` parameters.
|
||||
* We will take the Monday LDM to resolve the questions around conditional execution and out-of-order parameter execution (and hopefully all other
|
||||
open questions on this proposal).
|
52
meetings/2021/LDM-2021-04-19.md
Normal file
52
meetings/2021/LDM-2021-04-19.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
# C# Language Design Meeting for April 19th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I'm glad you had a stomachache here so I don't have to"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Today, we spent the full meeting digging into the pros and cons of conditional evaluation of interpolation holes, to ensure that we feel
|
||||
comfortable with the changes doing so will bring. The real challenge here is that conditional evaluation of interpolation holes will break
|
||||
with current mental models of interpolated strings: today, interpolated string holes are unconditionally evaluated in lexical order, and
|
||||
the way the proposal is designed makes it possible for a library update to change whether the expressions in an interpolation hole is
|
||||
evaluated or not. While library updates can always bring changes in behavior, this is the type of change that is currently only possible
|
||||
by a library adding the `Conditional` attribute onto a method or changing the type of an exception being thrown, causing a catch handler
|
||||
to no longer be hit. Adding a new instance method that is preferred over an extension method is similar, but except in rare cases involving
|
||||
user-defined conversions this can't actually affect the expressions in the method body itself.
|
||||
|
||||
If we proceed with conditional evaluation, user education will be a key component. The proposal does not involve conditional evaluation when
|
||||
the interpolated string literal is used directly as a `string`, but other types can introduce this. If a user is confused by this behavior,
|
||||
we need a clear thing we can point out to say "this is why you're seeing the current behavior." One potential way to do this is by introducing
|
||||
a specific syntax to enable conditional interpolation hole evaluation, something like `$?"{ConditionallyEvaluated()}"`. With such a syntax,
|
||||
we would never conditionally evaluate the holes unless the string were marked with `$?` or `@$?`. However, this immediately becomes a concern
|
||||
for libraries that want to introduce conditional evaluation: presumably they were making that decision for a good reason. A logging library
|
||||
would want this to just work, and every library that uses the feature would presumably then also need to make an analyzer to go along with the
|
||||
feature to ensure their customers are actually using it.
|
||||
|
||||
We also considered the benefits that partial evaluation gives the user. An important goal for C# features is that users don't adopt the
|
||||
feature then immediately switch to something else because it introduces performance issues. Patterns are a good example of doing this right:
|
||||
the compiler generates code that is usually as good, if not better, than the code the user would write by hand to match a pattern. Today's
|
||||
interpolated strings, on the other hand, do _not_ do this today. They box value types, don't work with spans, and generate array garbage that
|
||||
can't be elided. We want interpolated strings to be better: using the language feature should generate code that is just as performant as
|
||||
you can get manually today. Conditional evaluation is a big part of this: both with the up front check, and with the intervening checks on
|
||||
each `Append` call, we can ensure that a simple line of C# generates code that is as good as manual checks.
|
||||
|
||||
A good analogy to think about for concerns about conditional evaluation is in `struct`s: if C# were to introduce value types today, would
|
||||
we be concerned that we need to call out the copies everywhere they can occur? Or would we simply accept that, yes, struct copies can happen
|
||||
and that it's fine. Yes, behavior could change inside method bodies on upgrading a library, but that can happen in a number of ways with any
|
||||
library upgrade today. New instance methods can be introduced that cause extension methods to longer be chosen, libraries can introduce new
|
||||
conversions, new exceptions can be thrown/existing exceptions can be no longer thrown. While is another change that can happen, we don't think
|
||||
it's substantially different enough to warrant concern.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We accept conditional evaluation of interpolation holes as written in the spec.
|
77
meetings/2021/LDM-2021-04-21.md
Normal file
77
meetings/2021/LDM-2021-04-21.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# C# Language Design Meeting for April 21st, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Inferred types for lambdas and method groups](#inferred-types-for-lambdas-and-method-groups)
|
||||
2. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "And then we will open Pandora's box"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Inferred types for lambdas and method groups
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md
|
||||
https://github.com/dotnet/csharplang/issues/4674
|
||||
|
||||
Further implementation work has revealed a few potential breaking changes in giving lambdas and method groups a natural type (linked in the issue
|
||||
above). It is possible to run into these breaks with or without extension methods involved, and while these are the same types of breaks that can
|
||||
be observed in extension method resolution (adding new ones in a closer scope or adding an instance method that is preferred), these would be a
|
||||
case of the language actually changing the results of overload resolution for the same inputs. We have a few ideas for how to approach this:
|
||||
|
||||
1. Accept the break. This would be one of the bigger breaks that C# has ever taken, on par with the `foreach`-scoping changes in C# 5.
|
||||
2. Always prefer a target-type for method groups and lambda expressions. This idea, while sounding simple, is quite complex implementation-wise.
|
||||
The C# type inference and overload resolution algorithms are very much tied to having a natural type for an expression, and flowing that information
|
||||
through the process. To prefer target-typing here, we would have to first try to target-type, then fall back to a natural type and try the whole
|
||||
process again. This would add a significant cost complexity to an already-expensive part of the compilation process, and while it might work it
|
||||
will be an exponentially harder problem every time we want to do something else like this.
|
||||
3. Make natural types for lambdas and method groups opt-in. This would be through some sort of syntax gesture, such as:
|
||||
1. A cast like `(_)` to indicate "I would like the natural type for this but I don't want to state it". This would lock in a specific syntax
|
||||
that would forever need to be explained to users to "use C# 10 please".
|
||||
2. Only lambdas with return types (indicating new feature uptake) would have a natural type. This would severely hamper the scenarios our
|
||||
partner teams are interested in: most of the time, their return types should be inferred, and it will not support method groups.
|
||||
4. Only infer a natural type when converting to `System.Delegate` or `System.MulticastDelegate`. This technically still does have the chance of
|
||||
breaking scenarios, but the breaks are significantly smaller. However, this would result in the unfortunate consequence that only non-generic
|
||||
delegate types can cause natural types to be inferred, which feels like a pre-generics solution to the problem.
|
||||
|
||||
While accepting a break here could potentially be big, it's important to note that this particular scenario is actually quite fragile today. In
|
||||
either of the code samples, simply extracting the lambda or method group to a local variable will cause overload resolution to pick the same
|
||||
overload that giving the lambda a natural type would. This is because the local variable will have a natural type itself. It seems relatively
|
||||
likely that the only users intentionally taking advantage of this "feature" are creating extension methods that just call the original method
|
||||
with the delegate, using the extension method as a source of a target-type for the lambda expression.
|
||||
|
||||
We also have a decent amount of runway for previewing changes here. We're not planning on shipping this feature in a non-preview version of either
|
||||
VS or the runtime until it's time to actually release C# 10, so we can make an aggressive version of this change now and see if we need to dial
|
||||
it back. The preview will have a warning reported whenever the natural type of a lambda or method group is used, which will hopefully give us
|
||||
enough of a signal during this preview to know just how aggressive we can be here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will tentatively accept the breaking change to overload resolution. Previews will be used to determine if we need to revisit this decision.
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
We took a look at a couple of outstanding issues in interpolated strings today. First, we looked at the order of arguments to the builder from the
|
||||
current context. There is potential here to break lexical ordering of expressions if we allow arguments to the builder's `Create` method to come
|
||||
after the builder itself in the argument list. While a possible model for thinking of interpolation holes is lambda expressions, where the holes
|
||||
are either not evaluated or evaluated at a later point from where they were written, these holes can potentially have definite assignment impacts
|
||||
on the current method body. This is entirely different from other forms of deferred execution in C#, and we're concerned with the implication.
|
||||
After discussion, we agreed on requiring lexical ordering to be respected: if a parameter is an argument to the `Create` method of the builder type,
|
||||
it must come before the interpolated string. That error will be reported at the invocation site, and it should be affected by the true lexical
|
||||
order of the invocation. Named parameters can be used to reorder evaluation, so we should error if named parameter use results in an argument being
|
||||
evaluated after the interpolated string that needs it. We will implement a warning at the method declaration if the signature of the method requires
|
||||
named parameters to need to be used at the call site.
|
||||
|
||||
We also looked at the proposal around allowing interpolated strings to be "seen" through conversion expressions and through binary addition expressions.
|
||||
Binary addition being proposed resulted from prototyping work in the runtime to use the builder, while the conversion proposal was a logical next step.
|
||||
After discussion, we don't think that the use case of needing to disambiguate between two overloads with different builder types and otherwise identical
|
||||
signatures is realistic. We accept the use case for binary addition, but we will only do it for conversions to interpolated builder types as we don't
|
||||
have an ask for `IFormattable` or `FormattableString` here.
|
||||
|
||||
#### Conclusions
|
||||
|
||||
Lexical ordering should be respected, and seeing interpolated strings through binary addition expressions will be supported.
|
125
meetings/2021/LDM-2021-04-28.md
Normal file
125
meetings/2021/LDM-2021-04-28.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
# C# Language Design Meeting for April 28th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Open questions in record and parameterless structs](#open-questions-in-record-and-parameterless-structs)
|
||||
2. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I was kinda hoping you'd fight me on this"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Open questions in record and parameterless structs
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md
|
||||
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/record-structs.md
|
||||
|
||||
#### Initializers with explicit constructors
|
||||
|
||||
The first thing we need to resolve today is a question around our handling of field initializers, both in the presence of an
|
||||
explicitly-declared constructor(s), and when no constructors are present. There's two parts to this question:
|
||||
|
||||
1. Should we synthesize a constructor to run the field initializer? and
|
||||
2. If we don't, should we warn the user that their field initializer won't be run?
|
||||
|
||||
We feel that a simple rule here would be to mirror the observed behavior with reference types: if there is no explicit constructor
|
||||
for a struct type, we will synthesize that constructor. If that constructor happens to be empty (as it would be today in a
|
||||
constructorless struct type because field initializers aren't yet supported) we optimize that constructor away. If a constructor
|
||||
is explicitly declared, we will not synthesize any constructor for the type, and field initializers will not be run by `new S()`
|
||||
unless the parameterless constructor is also explicitly declared. This does have a potential pit of failure where users would
|
||||
expect the parameterless constructor to run the field initializers, but synthesizing a parameterless constructor would have bad
|
||||
knock-on effects for record structs with a primary constructor: what would the parameterless constructor do there? It doesn't
|
||||
have anything it can call the primary constructor with, and would result in confusing semantics. To address this potential issue,
|
||||
we think there is room for a warning wave dedicated to using `new S()`, when `S` does not have a parameterless constructor. There
|
||||
will be some holes in this, particularly around generics: `struct` today implies `new()`, and we're concerned about how breaking
|
||||
the change would be if we tried to make the warning apply to everywhere that a parameterless struct was substituted for a type
|
||||
parameter constrained to `struct`. We would also have to take another language feature to enable `where T : struct, new()`, which
|
||||
isn't allowed today. If there is future appetite for introducing another warning wave to cover the generic hole, we can look at
|
||||
it at that point.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will only synthesize a constructor when the user does not explicitly declare one. We will consider a warning wave when using
|
||||
`new` on a struct that does not have a parameterless constructor and also has an explicit constructor with parameters.
|
||||
|
||||
#### Record struct primary constructors
|
||||
|
||||
Next, we looked at how the parameterless struct constructor feature will interact with record primary constructors. The rule we
|
||||
decided for the first question ends up making the decisions very simple here:
|
||||
|
||||
1. Parameterless primary constructors are allowed on struct types.
|
||||
2. The rules for whether an explicit constructor needs to call the primary constructor are the same as in record class types. This
|
||||
applies to an explicit primary constructor too, just like in record class types.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Use the above rules.
|
||||
|
||||
#### Generating the Equals method with `in`
|
||||
|
||||
We looked at a potential optimization around the `Equals` method, where we could generate it with an `in` parameter, instead of
|
||||
with a by-value parameter. This could help scenarios with large struct types get better codegen. However, when we would want to do
|
||||
this is a very complicated heuristic. For structs smaller than machine pointer size, it is usually faster to pass by value. This
|
||||
gets even more complex when considering types that are passed in non-standard registers, such as vectorized code. We don't think
|
||||
there's a generalized way to do this heuristic. Instead, we just need to make sure that the user can perform this optimization, if
|
||||
they want to.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We won't attempt to be smart here. Users can provider their more customized equality implementation if they so choose.
|
||||
|
||||
#### `readonly` Equality and GetHashCode
|
||||
|
||||
Finally in record structs, we looked at automatically marking the `Equals` and `GetHashCode` methods as `readonly`, if the all the
|
||||
fields they use for the calculation are also all `readonly`. While this would be technically feasible, we're not sure what the
|
||||
scenario for this is, beyond just marking the entire struct `readonly`. At that point, every method would be readonly, including
|
||||
the ones we synthesize.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We don't do anything smart here. Users can just mark the struct as `readonly`.
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
We're getting close the end of open questions in this proposal. We looked at 2 today:
|
||||
|
||||
#### Confirming `InterpolatedStringBuilderAttribute`
|
||||
|
||||
An internal discussion on simplifying the conversion rules resulted in a proposal that we only look for the presence of a specific
|
||||
attribute on a type to determine if there exists a conversion from an interpolated string literal to the type. This simplification
|
||||
results in much cleaner semantics: the presence of various methods on the builder type no longer plays into whether the conversion
|
||||
exists, only whether conversion is valid. This will help users get understandable errors that don't silently fall back to
|
||||
string-based overloads when a builder doesn't have the right set of `Append` methods to lower the interpolated string.
|
||||
|
||||
We also looked at whether we should control the final codegen based on a property on the attribute: we initially proposed conditional
|
||||
evaluation could be controlled by the attribute. This would potentially let the compiler change the behavior of when expressions in
|
||||
interpolation holes are evaluated: up front, or in line the `Append` calls. However, the logic for determining the codegen is not
|
||||
as simple as one property: the `Append` calls can potentially return `bool`s to stop evaluation, and the `Create` method can
|
||||
potentially `out` a parameter to control this as well. There are valid scenarios for all 4 possibilities here, so a switch that only
|
||||
allows either all conditional or no conditional isn't a good option. It's also complex because this would be a library author making
|
||||
a lowering decision for user code. We also note that, if we decide that this is important at a later date, we can extend the pattern
|
||||
then.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Using the attribute is accepted. The `Append` and `Create` method signatures will drive the lowering process.
|
||||
|
||||
#### Specific extensions for structured logging
|
||||
|
||||
Finally today, we looked at a question around including specific syntax to support structured logging. [Message templates](https://messagetemplates.org/)
|
||||
are a well-known structured logging format that most of the biggest .NET logging frameworks support, and as we want these libraries
|
||||
to consider using the improved interpolated strings feature, we looked to see if we can include specific syntax to help encourage
|
||||
this. After some initial discussion, our general sentiment is that this feels too narrow. An example syntax we considered is
|
||||
`$"{myExpression#structureName,alignment:format}"`, and while this would work for the scenario, it wouldn't be a meaningful improvement
|
||||
over simply using a tuple expression: `$"{("structureName", myExpression),alignment:format}"`. It is possible to construct interpolated
|
||||
string builders that only accept tuples of `string` and `T` in their interpolation holes, and with the other adjustments we made today
|
||||
there should be good diagnostics for such cases. Further restrictions can be imposed via analyzers, as is possible today. While a more
|
||||
general string templating system could be interesting, we think that this is a bit too narrow of a focus for a language feature today.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We won't pursue specific structured syntax at this time.
|
94
meetings/2021/LDM-2021-05-03.md
Normal file
94
meetings/2021/LDM-2021-05-03.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# C# Language Design Meeting for May 3rd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Improved interpolated strings](#improved-interpolated-strings)
|
||||
2. [Open questions in record structs](#open-questions-in-record-structs)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "You all have a special way of working"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Improved interpolated strings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4487
|
||||
|
||||
Today, we got through the last of the open questions around the improved interpolated strings feature proposal.
|
||||
|
||||
#### `Create` vs `.ctor`
|
||||
|
||||
The first open question is around using the `Create` method instead of a constructor call. The spec today uses a static `Create` method as
|
||||
an abstraction, to allow a hypothetical builder type to pool reference types in a static context, rather than being forced to return a `new`
|
||||
instance on every builder creation. However, while this is a nice abstraction, we don't know of any proposed builder types that would take
|
||||
advantage of this feature, and there are real codegen benefits to using a constructor instead. If we later come across a use case that would
|
||||
like a static `Create` method instead, we can also add it later by using a switch on the attribute.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
For better codegen, we will go with a single method of creation, a constructor, not a `Create` method.
|
||||
|
||||
#### Converting regular strings to builder types
|
||||
|
||||
We could consider any `string` value as being convertible to a builder type automatically via the builder pattern, which is the trivial case
|
||||
of `value.Length` number of characters with zero interpolation holes. If we were to do this, it would be in the interest of consistency: a
|
||||
user could more readily reason about how all string types will interact with an API. However, we do already have a mechanism in the language
|
||||
to enable this: implicit user-defined conversions. Pushing a string through the standard builder pattern will likely be worse for performance
|
||||
than what an API-author would write in an implicit conversion, and user-defined conversions already have well-defined semantics and impacts
|
||||
on overload resolution.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. If an API author wants string to be convertible to their builders, they can use a user-defined conversion.
|
||||
|
||||
#### `ref struct` builder types in `async` contexts
|
||||
|
||||
Today, `ref struct` types are generally forbbiden in `async` contexts. This is because `await` expressions can occur in any subexpression, and
|
||||
we have not formalized rules around ensuring that a `ref struct` variable does not cross an `await` boundary. In order to enable `ref struct`
|
||||
builder types in `async` methods, we'd need to do essentially that same work in order to make usage safe. We're not introducing any new tradeoffs
|
||||
by avoiding this: already today, developers need to choose between using a `ref struct`, and allowing usage in `async` contexts. While we would
|
||||
be interested in general rules here, we don't think we should do anything special with regard to interpolated strings builder types. If we enable
|
||||
it in general, interpolated string builders will naturally come along for the ride.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will treat interpolated string builder usage as any other usage of a `ref struct` in `async` methods (today, that means disallowed).
|
||||
|
||||
### Open questions in record structs
|
||||
|
||||
#### Implementing `ISpanFormattable`
|
||||
|
||||
The BCL is adding a new interface for .NET 6, `ISpanFormattable`, which allows types to be formatted directly into a span, rather than having to
|
||||
create a separate string instance. One immediate thought we have is why are records (struct or class) special here? We often think of records as
|
||||
being named versions of anonymous types/tuples, so if we were to do this for records we would likely want to do this for those types as well.
|
||||
This is important because it does impact public API: new overloads of `PrintMembers` will need to be implemented, and how this will interact with
|
||||
the existing `PrintMembers` method that takes a `StringBuilder` is unclear. We also don't have a clear scenario here: `ToString()` in records is
|
||||
mainly focused around debugging and logging scenarios. While performance is a nice to have here, it's not an overridding concern, and users who
|
||||
want to actually use `ToString()` to do more important work can provide their own implementation, and `ISpanFormattable` with it. They can handle
|
||||
the problems around interop with existing APIs by simply not using those APIs, which will keep the considerations simpler for them.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
#### Using `InterpolatedStringBuilder` in record formatting
|
||||
|
||||
We could potentially optimize the implementation of `ToString()` by using `InterpolatedStringBuilder` as the string-building mechanism, rather
|
||||
than `StringBuilder`. Unfortunately, this again runs into the problem of needing to interop with existing code, which unfortunately limits our
|
||||
options here. Without a clear scenario to try and solve, we don't think this is worth it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
#### Passing an `IFormatProvider`
|
||||
|
||||
We could potentially generate an overload of `ToString` that takes an `IFormatProvider` and passes it to nested contexts. This again runs the
|
||||
question of lacking a clear scenario and interop with existing code. We additionally don't have a clear idea of when we'd pass the format
|
||||
provider to a nested type, as there is no standardized `ToString(IFormatProvider)` method on all types. If a user wants to have formatted
|
||||
strings in their records, they presumably are providing their own implementation anyway, so we don't feel this is appropriate.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
95
meetings/2021/LDM-2021-05-10.md
Normal file
95
meetings/2021/LDM-2021-05-10.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# C# Language Design Meeting for May 10th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Lambda improvements](#lambda-improvements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I'll allow it"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Lambda improvements
|
||||
|
||||
#### Lambda natural types in unconstrained scenarios
|
||||
|
||||
There are a few related open questions around how we handle lambda expressions and method groups in unconstrained scenarios. These
|
||||
are scenarios such as:
|
||||
|
||||
```cs
|
||||
var a = (int x) => x; // What is the type of A?
|
||||
|
||||
void M1<T>(T t);
|
||||
M1((int x) => x); // What is the type of T?
|
||||
|
||||
void M2(object o);
|
||||
M2((int x) => x); // What is the type of the expression? ie, o.GetType() returns what?
|
||||
```
|
||||
|
||||
These scenarios are all related to how much we want to define a "default" function type in C#, and how much we think doing so could
|
||||
stifle later development around value delegates (if we even do such a feature). In C# today, we have 3 function types:
|
||||
|
||||
* Delegate types that inherit from `System.Delegate`.
|
||||
* Expression tree types that inherit from `System.Linq.Expressions.**Expression`.
|
||||
* Function pointer types.
|
||||
|
||||
All of these types support conversions from some subset of method groups or lambdas, and none is currently privileged above another.
|
||||
Overloads between these types are considered ambiguous, and users must explicitly include information that tells the compiler what
|
||||
type of function type to use, such as by giving a target type. If we allow `var`, conversions to `object`, and/or unconstrained generic
|
||||
type inference to work, we would be setting in stone what the "default" function type in C# is. This would be particularly noticeable
|
||||
if our future selves introduced a form of lightweight delegate types and wanted to make them the default in various forms of overload
|
||||
resolution. We are doing analogous work with interpolated strings currently, but interpolated strings are a much smaller section of
|
||||
the language than lambda expressions, and lambda irregularities are potentially much more noticeable.
|
||||
|
||||
We could protect our future selves here by making the spec more restrictive: Do not allow lambdas/method groups to be converted to
|
||||
unconstrained contexts. This would mean no `var`, no unconstrained type parameters, and no conversion to `object`. We would only infer
|
||||
when there was information that we could use to inform the compiler as to which type of function type to use: conversion to just
|
||||
`System.Delegate` would be fine, for example, because we know that the delegate type version was being chosen. While this would protect
|
||||
the ability to introduce a value delegate type later and make it the default, we see some potential usability concerns with making
|
||||
such a delegate type the default. At this point, we believe such a delegate type would be based on ref structs, and making `var`
|
||||
declare these types would be minefield for async and other existing scenarios. Using such types by default in generic inference would
|
||||
have similar issues around ref struct safety rules. And finally, if such a type were converted to `object`, it would by necessity need
|
||||
to be boxed somewhere, obviating the point of using a lightweight value delegate type in the first place. Given these concerns, we
|
||||
believe that we would just be protecting our ability to make the same decision later, and that another function type would not be a
|
||||
good "default".
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We allow inferring a natural type for a lambda or method group in unconstrained scenarios, inferring Action/Func/synthesized delegate
|
||||
type, as appropriate.
|
||||
|
||||
#### Type inference
|
||||
|
||||
We went over the rules as specified in the proposal. The only missing bit is that, at final usage, a function type needs to look at
|
||||
constraints to determine whether it should be inferred to be a Delegate or an Expression.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Accepted, with the additional step around constraints.
|
||||
|
||||
#### Direct invocation
|
||||
|
||||
Finally today, we looked at supporting direct invocation of lambda expressions. This is somewhat related to the
|
||||
[first topic](#lambda-natural-types-in-unconstrained-scenarios), but could be implemented even if we chose to do the restrictive version
|
||||
of that issue because this feature would not require us to actually choose a final function type for the lambda expression. We could
|
||||
just emit it as a method and call it directly, without creating a delegate instance behind the scenes at all. However, we don't have an
|
||||
important driving scenario behind this feature: technically it could be used as a form of statement expression, but it doesn't feel like
|
||||
a good solution to that problem. The main thing we want to make sure works is regularity with other inferred contexts:
|
||||
|
||||
```cs
|
||||
// If this works
|
||||
var zeroF = (int x) => x;
|
||||
var zero = zeroF(0);
|
||||
|
||||
// This should also work
|
||||
var zero = ((int x) => x)(0);
|
||||
|
||||
// But this wouldn't work with var, so is it fine to have not work here?
|
||||
var zero = (x => x)(0);
|
||||
```
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We generally support the idea, even the final example. It may take more work however, and thus may not make C# 10. We'll create a
|
||||
more formal specification for approval.
|
95
meetings/2021/LDM-2021-05-12.md
Normal file
95
meetings/2021/LDM-2021-05-12.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
# C# Language Design Meeting for May 12th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Experimental attribute](#experimental-attribute)
|
||||
2. [Simple C# programs](#simple-c-programs)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I've always felt the shotput with the Rubik's Cube should be part of the Olympics"
|
||||
|
||||
## Discussion
|
||||
|
||||
Today we took a look at a couple of broader .NET designs that will impact C#, to get an idea of how we feel C# fits into these designs.
|
||||
|
||||
### Experimental attribute
|
||||
|
||||
https://github.com/dotnet/designs/blob/main/accepted/2021/preview-features/preview-features.md
|
||||
|
||||
The first proposal we looked at is the design the runtime team has been working on around preview features. The C# compiler has shipped
|
||||
preview features in the compiler ahead of language releases before, but this hasn't been very granular, and we've never shipped a version
|
||||
of the runtime (particularly not an LTS!) where some parts of the runtime and language features built on top are actually still experimental.
|
||||
Support stories get complicated, particularly when you start mixing installations of LTS versions of .NET 6 and .NET 7 on the same machine.
|
||||
For example, we'll be shipping a preview of abstract static members in interfaces in .NET 6, but this implementation will _always_ be a
|
||||
preview. It's possible that, for .NET 7, we'll move the feature out of preview, and the version of the C# compiler in VS installed at that
|
||||
point would no longer consider abstract statics to be a preview language feature, even if the project itself is targetting .NET 6. To solve
|
||||
this, the runtime will introduce a new attribute, detailed in the preview feature proposal, which marks APIs and elements of RuntimeFeature
|
||||
that should be considered preview in this version of the runtime, and then require that the consuming library be marked preview itself.
|
||||
Where this requirement comes from is one of the open questions in this meeting: should it come from an analyzer or from the compiler itself?
|
||||
|
||||
The analyzer approach is initially attractive because it is easier to implement. Roslyn's analyzer infrastructure, particularly with the
|
||||
investments around both the symbol analyzer model and IOperation, means that it is possible to implement this analyzer almost entirely
|
||||
language-independently, while a compiler feature would have be implemented in each compiler separately. It is also significantly easier to
|
||||
maintain an analyzer: turning off analyzer errors is possible for false-positives while bugs are patched, while compiler errors are impossible
|
||||
to disable. However, this flexibility does come with downsides: it's possible to disable the analyzer for _real_ positives and ship in an
|
||||
unsupported state, and potentially cause downstream dependencies to take advantage of preview features unintentionally. There's also no
|
||||
guarantees that users actually enable the analyzer, as they might just disable analyzers for any particular reason. Despite these concerns,
|
||||
we think that an analyzer is a good path forward, at least initially. We can use an analyzer in .NET 6 to iron out the semantics of how the
|
||||
feature works, and look at putting the logic into the compiler itself in a future version.
|
||||
|
||||
In particular, there are some interesting semantics that still need to be worked out. Should APIs be allowed to introduce or remove previewness
|
||||
when overriding or implementing a member? For example, `System.Decimal` already has a `+` operator today, and it will be implementing the
|
||||
preview numeric interfaces that define the `+` abstract static operator. The `+` on System.Decimal is not preview today, nor should it be in
|
||||
.NET 6, but it _will_ be implementing the preview `+` operator. Explicit implementation is also not always possible for these operators, as
|
||||
we will have asymmetric operator support in the math interfaces that get unified with a symmetric operator later in the hierarchy, so we
|
||||
can't rely on enforcing explicit implementation to solve this problem. On the opposite side of this problem, allowing adding of previewness
|
||||
at more derived API level is problematic, because the user could be calling a less-derived method and therefore accidentally taking advantage
|
||||
of a preview feature.
|
||||
|
||||
Finally, there is some IDE-experience work to think through. While we do want these APIs to show up in places like completion and refactorings,
|
||||
we would also like to make sure we're not accidentally opting users into preview features that then break their build totally unexpectedly. We
|
||||
think there is design space similar to our handling of `Obsolete`, such as marking things preview in completion lists.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will proceed with an analyzer for now and look to move that logic into the compiler at a later point, if we think it makes sense. More
|
||||
design is needed on some parts of the feature, but it doesn't require direct involvement of the LDM.
|
||||
|
||||
### Simple C# programs
|
||||
|
||||
https://github.com/dotnet/designs/pull/213
|
||||
|
||||
Finally today, we looked at "simple" C# programs. We take simple here to mean programs that don't have a lot of complex build logic, and are
|
||||
possibly on the smaller side code-wise. Simple does _not_ necessarily mean "Hello, World!" and nothing else. While we are interested in the
|
||||
intro experience, we additionally think there is opportunity to expand to address a market that has traditionally had a bunch of friction to
|
||||
use C# in today.
|
||||
|
||||
C# has historically had a focus on very enterprise scenarios: professional developers working on larger projects using a dedicated IDE. Our
|
||||
user studies have shown that this workflow isn't what many newer users are expecting, particularly if they're coming from scripting languages
|
||||
like Go, Javascript, or Python. These users instead expect to be able to simple make a `.cs` file and run it, with potentially more ceremony
|
||||
as they start adding more complex dependencies or other scenarios. Other expectations exist (such as repls), but our studies have shown that
|
||||
this is the most popular. An important part of our task here will be figuring out where that "more ceremony" step lies, what that additional
|
||||
ceremony will look like, and how it will interact with any additions we make to the language (how project-file based projects interact with
|
||||
`#r` directives, for example).
|
||||
|
||||
Investing in tooling that puts NuGet directives in C#, as well as potentially `#load` or other similar file-based directives, is going to
|
||||
necessitate that we reconcile file structure and project structure in C#. Today, the contents of C# files are very intentionally independent
|
||||
of their locations on disk: file structure is handled by the project file, and project structure is handled by `using` directives and
|
||||
namespace declarations. `#r` to local NuGet packages or `#load` to add other `.cs` files would blur the line here.
|
||||
|
||||
Another important consideration of these directives is how complex we want to let them get. At the extreme end, these directives could be
|
||||
nested inside `#if` directives, which starts to necessitate a true preprocessing step that the SDK tooling will need to understand and perform.
|
||||
Today, preprocessor directives in C# don't have massive effects, and they can be processed in line with lexing. There are restrictions we can
|
||||
look at for where to allow `#r` and similar directives, and deciding on those restrictions will help inform where exactly that additional
|
||||
ceremony will land. For example, we could require that all of these directives must be the first things in a file, with nothing preceding
|
||||
them. This would ensure that conditional NuGet references are that cliff of complexity that requires a project file.
|
||||
|
||||
Finally, how much of the project settings do we think is reasonable to control in a .CS file? Is it reasonable to set language version or TFM
|
||||
in a file? What about output settings such as dll vs exe, or single-file and trimming settings? We don't have answers for these today, and
|
||||
some of these answers will be driven by discussions with the SDK teams, but they're all part of determining where the cliff of complexity
|
||||
will land.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Overall, we're extremely excited to take this challenge on, and look forward to working through this design to find the edges.
|
125
meetings/2021/LDM-2021-05-17.md
Normal file
125
meetings/2021/LDM-2021-05-17.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
# C# Language Design Meeting for May 17th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Raw string literals](#raw-string-literals)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "You have a mongolian vowel separator"
|
||||
"Dear god"
|
||||
- "I was expecting [redacted] to flip the table and say I'm out of here"
|
||||
"When you flip the table at home, how do you clean that up?"
|
||||
"Exactly, I don't want to have to clean that up"
|
||||
"Wait wait, that sounds like a new teams feature"
|
||||
- "Pretty easy to interpolate those results"
|
||||
"Is that a correct use of the word interpolate?"
|
||||
"No"
|
||||
|
||||
## Discussion
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4304
|
||||
|
||||
Today we took a look through the proposal for raw string literals. This would add a new type of string literal to C#, specifically
|
||||
for working with embedded language scenarios that are difficult to work with in C# today. A number of other languages have added
|
||||
similar features, though our colleagues on Java are perhaps the most direct inspiration for this proposal (particularly with the
|
||||
whitespace trimming feature).
|
||||
|
||||
Overall, the LDM is universally in favor of the feature. Some details still remain to be worked out, but we believe this is a feature
|
||||
that will benefit C#, despite the complexity in adding yet another string literal format.
|
||||
|
||||
### Single-line vs multi-line
|
||||
|
||||
The proposal suggests both single-line and multi-line forms for this new literal format. While multi-line literals are the obvious
|
||||
headline feature here, we do think there's a use-case for a single-line form: embedded languages like regex are often single-line,
|
||||
used inline in a method call or similar. They suffer from all the same problems as other embedded languages today (frequent need
|
||||
for quotes, but using quotes is both hard and results in hard-to-read code).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We would like a single-line version.
|
||||
|
||||
### Whitespace trimming
|
||||
|
||||
Verbatim string literals today have behavior that make them somewhat unpleasant for multiline strings because, if whitespace is not
|
||||
desired at the start of the line, the literal content has to be fully start-aligned in the file. This leads to a zig-zag code pattern
|
||||
in the file which breaks up the flow of the file and can make subsequent indentation hard to judge. Trimming solves this by removing
|
||||
whitespace from the start of every line, determined by the whitespace preceding the final quotes. This feature has a couple of levers
|
||||
we can tune to increase or decrease the requirements on the user, namely around handling blank lines. The proposal currently says
|
||||
that blank lines must have a _prefix_ of the removed whitespace on the line. That means that, if the whitespace to be removed is
|
||||
`<tab> <tab> <space>`, a completely empty line is fine, as is a blank line with `<tab> <tab>`. Both are prefixes of the whitespace to
|
||||
be removed. However, a line with `<tab> <space>` is _not_ fine, because that is not a prefix of the whitespace to be removed. We
|
||||
could make this more strict by saying that a blank line must either have the full whitespace to be removed, or no content at all.
|
||||
While this is more strict, it could end up being a better user experience: whitespace is, by nature, hard to visualize, and providing
|
||||
a good diagnostic experience around "this whitespace isn't a prefix of that whitespace" is not something we're eager to tackle. On the
|
||||
other hand, trailing whitespace is pretty easy to accidentally insert today, and we don't make that a compile error by default anywhere
|
||||
else.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with the stricter behavior for now: blank lines must either contain the entire whitespace being removed, or no text at all.
|
||||
We can relax this later if it makes the experience better.
|
||||
|
||||
### Language fences
|
||||
|
||||
We also looked at allowing a language fence in literals, similar to how markdown works. In multiline markdown strings, ```` ```identifier ````
|
||||
marks the code block as having a specific language, which the markdown renderer can use to render the text inline. In C# string literals
|
||||
today, there's fragmented support for this implemented in a number of tools: VS, for example, detects when a string is being passed
|
||||
directly to a regex API, and also has support for a comment syntax to turn on highlighting for different locations. We could standardize
|
||||
this for C# raw string literals: the proposal is specifically worded such that text is _required_ to start on the line after the open
|
||||
quotes to allow us to include this feature in the future. One immediate question, however, is how would we support this for single-line
|
||||
literals. Our existing syntax highlighting support is specifically for regex, which is the exact use case for the single-line version,
|
||||
but the language specifier doesn't work there. We could potentially support a trailing language specifier for this case, such as
|
||||
`""".*"?"""regex`, but it would limit the number of things that can be put in the space.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're mixed on language fences, leaning against supporting them for now. More debate is needed.
|
||||
|
||||
### Interpolation
|
||||
|
||||
This proposal didn't originally have interpolation, but after a large pushback from the community, interpolation was added. Because the
|
||||
goal of the proposal is to represent all strings without escaping, the immediate next question is how we represent interpolation holes
|
||||
without requiring escaping of braces. The proposal suggests we use the number of `$`s in front of the raw string to represent the number
|
||||
of braces required to trigger an interpolation hole: `$$"""{}"""` would be the string `{}`, because `{{}}` is needed to be counted as an
|
||||
interpolation hole. IDE experience is going to be very important here: context-sensitive interpolation holes are going to be somewhat
|
||||
harder to keep track of, and refactorings to add additional braces to an existing string if needed will be very helpful for users to avoid
|
||||
tedious and potentially error-prone manual changes when suddenly the user needs to use the existing number of braces as an actual string
|
||||
component.
|
||||
|
||||
We also considered a slightly different form, `$"""{{`, where the number of braces after the triple quotes controls how interpolations work.
|
||||
This form, while providing a more direct representation of the number of curlies required, doesn't work for single-line strings and cannot
|
||||
be applied to all interpolated strings. We further thought about using the number of quotes to control both the number of braces required
|
||||
for interpolation holes and the number of quotes required to close the string; while this would work for the single-line form, it would
|
||||
require that all interpolation holes are a minimum of 3 braces, but most scenarios we can think of either don't need braces, or only need
|
||||
to represent a single open/close brace. It also cannot be extended to all interpolated strings.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We want interpolation, and we're ok with using the number of `$`s to control the number of braces.
|
||||
|
||||
### Alternative Quotes
|
||||
|
||||
We considered whether to use ```` ``` ```` or `'''` instead of or in addition to `"""`. The `` ` `` symbol is concerning because it can
|
||||
be hard for non-English keyboard layouts to hit; while there are symbols in C# today that are already difficult for these layouts to hit,
|
||||
we don't want to deliberately introduce more pain for these users. We also don't like the complexity of either having multiple symbols
|
||||
that can start strings in C#: this proposal is already adding an axis of complexity (for gain we feel is worth it), but we don't think
|
||||
the additional axes of complexity is worth the tradeoff here. It does mean that the single-line version cannot represent a string that
|
||||
starts with a `"`, but we think this is an OK tradeoff.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Quotes are the only way to start strings. Users that need to start a string with a quote must use the multi-line version.
|
||||
|
||||
### Miscellaneous conclusions
|
||||
|
||||
Even though we could support parsing long strings of quotes on a non-blank line inside a raw string literal, we will require that if a
|
||||
user wants to use 4 quotes in a string, the raw string delimiters must be at least 5 quotes long.
|
||||
|
||||
Strings like this are supported:
|
||||
```cs
|
||||
var z = $$"""
|
||||
{{{1 + 1}}}
|
||||
""";
|
||||
```
|
||||
The innermost braces are the interpolation holes, the resulting value here would be `{2}`.
|
121
meetings/2021/LDM-2021-05-19.md
Normal file
121
meetings/2021/LDM-2021-05-19.md
Normal file
|
@ -0,0 +1,121 @@
|
|||
# C# Language Design Meeting for May 19th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Triage](#triage)
|
||||
1. [Checked operators](#checked-operators)
|
||||
2. [Relaxing shift operator requirements](#relaxing-shift-operator-requirements)
|
||||
3. [Unsigned right shift operator](#unsigned-right-shift-operator)
|
||||
4. [Opaque parameters](#opaque-parameters)
|
||||
5. [Column mapping directive](#column-mapping-directive)
|
||||
6. [Only allow lexical keywords](#only-allow-lexical-keywords)
|
||||
7. [Allow nullable types in declaration patterns](#allow-nullable-types-in-declaration-patterns)
|
||||
2. [Protected interface methods](#protected-interface-methods)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I feel like I've known this before and then I packed it away in some chamber of horrors in my brain"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Triage
|
||||
|
||||
#### Checked operators
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4665
|
||||
|
||||
There are some complexities to this proposal (such as betterness rules between checked and unchecked operators), how it will affect VB, and some
|
||||
potential clunkiness in the interface definitions (`int` shift doesn't overflow today even in checked contexts, but would need to expose both?),
|
||||
but we think that this is pretty essential to a well-formed generic math interface structure.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set.
|
||||
|
||||
#### Relaxing shift operator requirements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4666
|
||||
|
||||
We have some immediate visceral reactions to this, but it's been 21+ years of BCL and other library design and we don't see huge abuse of other
|
||||
operators. It might be time to lift the restriction here.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set.
|
||||
|
||||
#### Unsigned right shift operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4682
|
||||
|
||||
This is an odd missing operator in general, and a hole in our design for generic math that similar libraries in other languages have filled.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set.
|
||||
|
||||
#### Opaque parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4629
|
||||
|
||||
We're not a huge fan of how this silently munges with the method signature, and the number of cliffs there are: what happens when a user wants
|
||||
2 of these parameters with the same type, or wants to add multiple constraints to a type parameter?
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. It's possible we could revisit flavors of this with associated types at a later point, but as is this is rejected.
|
||||
|
||||
#### Column mapping directive
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4747
|
||||
|
||||
We have a few open questions, such as whether we need an end offset as well. However, overall this looks good. These directives are basically
|
||||
never human-written or read, and it helps solve problems for partner teams using C# as a DSL.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set.
|
||||
|
||||
#### Only allow lexical keywords
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4460
|
||||
|
||||
This is a discussion that has been building in the LDM for years, particularly around older contextual keywords such as `var` and `dynamic`
|
||||
used in a type position. We think there are two broad categories of contextual keywords here: keywords that we think are "safe" to reserve,
|
||||
such as the aforementioned `var`, that are used in positions where standard C# conventions wouldn't allow things to be named in a conflicting
|
||||
manner: types, for example, are PascalCase by convention in C#, and `var` starts with a lowercase character. Making a break here helps _both_
|
||||
the compiler team and the average language user, as it simplifies the language and isn't likely to break code that isn't intentionally trying
|
||||
to avoid the feature. There are other keywords though, such as `yield`, that we think are good to keep as contextual keywords. It makes the
|
||||
compiler team's life more difficult, but it helps users, and we don't want to make changes here just to make the compiler's job a bit easier.
|
||||
We think there's opportunity to do work here in the first set, particularly if we take a phased approach where we warn about a keyword in C#
|
||||
X and then totally deprecate in Y.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set. We'll revisit soon to think about the phased strategy and see what we want to do for C# 10.
|
||||
|
||||
#### Allow nullable types in declaration patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4724
|
||||
|
||||
There are some really gnarly parsing ambiguities here, but even if we could solve the compiler parsing problem, the human parsing problem will
|
||||
remain. We don't think those problems are really solveable, and that the gain isn't worth the complexity.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
### Protected interface methods
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4718
|
||||
|
||||
When DIMs were initially implemented, we were concerned about a `public` member in a type named the same as a `protected` member in an interface
|
||||
that type is implementing being confused for an implicit implementation of that `protected` member. However, this ends up somewhat hindering the
|
||||
goal of DIMs in the first place, which was making adding new methods to an interface not be a breaking change. Given this, and given that the
|
||||
future option for _having_ implicit `protected` interface member implementation was already removed in V1 of DIMs, we'd like to remove this
|
||||
restriction. We don't think we can treat this as a bugfix, despite the low impact nature, as it was intentional and C# 8 has been out for a year
|
||||
and a half now. The hardest part will be coming up with a name for the compiler error message: perhaps "Relaxed DIM requirements for non-public
|
||||
members".
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll relax this restriction as a new language feature.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue